D1T1:铺设道路

回忆NOIP2013D2T1 积木大赛,发现这两题唯一的区别就是一个是造山一个是填坑,而把填坑的操作反序就是造山,所以可以直接使用那道题的方法。

具体方法是,从左到右每次考虑新的一列,若这一列的坑比左边一列浅,那么可以在填左边一列的时候顺便填好这个坑(只要把所有右端点为i-1的操作右端点全部改为i即可),不需要任何操作。若这一列的坑比左边深,那么就必须先将这一列的坑填到与左边平齐,再让左边的操作顺带把这个坑填平。

于是有:若a[i]<=a[i-1],ans不变,否则ans+=a[i]-a[i-1]。

期望得分:100

复杂度:$O(n)$

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=;
int n,ans,a[N]; int main(){
freopen("road.in","r",stdin);
freopen("road.out","w",stdout);
scanf("%d",&n);
rep(i,,n){
scanf("%d",&a[i]);
if (a[i]>a[i-]) ans+=a[i]-a[i-];
}
printf("%d\n",ans);
return ;
}

road

D1T2:货币系统

这题性质十分类似线性基的构造,首先证明两个结论:

1.最简系统一定是原系统的子集。

2.最简系统中任何一个面额不能被其余面额表出。

结论二显然。

若最简系统出现了一个原系统没有的数x,那么若这个数能被原系统表出,那么它的加入没有任何意义,可以删去,矛盾。

若不能被表出,那么最简系统就比原系统多表出了一个数x,矛盾。

结论一得证。

于是就有了运行策略,先将原系统中所有数从小到大排序(因为能表出某数的面额一定都不比那个数大),逐一判断每个数能否被当前最简系统表出,若不能则加入。

那么如何判断能否被表出呢,注意到值域不大,那么这个题的原型实际上就是一个完全背包问题,于是每次加入数的时候做一次完全背包即可。

期望得分:100

复杂度:$O(n*a_i)$

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=;
int T,n,a[];
bool f[N]; int main(){
freopen("money.in","r",stdin);
freopen("money.out","w",stdout);
for (scanf("%d",&T); T--; ){
scanf("%d",&n); int mx=,ans=;
rep(i,,n) scanf("%d",&a[i]),mx=max(mx,a[i]);
rep(i,,mx) f[i]=; f[]=;
sort(a+,a+n+);
rep(i,,n){
if (f[a[i]]) continue;
ans++;
rep(j,a[i],mx) f[j]|=f[j-a[i]];
}
printf("%d\n",ans);
}
return ;
}

money

D1T3:赛道修建

十分套路的一道题。

算法一:m=1的情况就是要找一条直径。

期望得分:20

复杂度:$O(n)$

算法二:ai=1是一个菊花图,二分答案后尽量匹配出最多的长度>=mid的链。这个贪心或再套一层二分答案均可。

期望得分:20

复杂度:$O(n\log n)$

算法三:bi=ai+1是一条链,二分答案后贪心选取即可。若当前链长度已>=mid则断开,计数器+1。

期望得分:20

复杂度:$O(n\log n)$

以上部分通过数据分治可以得到55分。

算法四:分支不超过3,只要找到一个度为1的点作为根就是一棵二叉树。在树上做简单DP即可,具体可见满分做法。

期望得分:55

复杂度:$O(n\log n)$

以上部分分通过数据分治可以得到80分。

算法五:首先想到二分答案转化为可行性问题。问题转化为,能否找到至少m条长度不小于mid的不相交的道路。

考虑树形DP,这个问题要求最大化两个指标,一是道路条数最大化,二是道路条数最多的情况下每条道路长度最长。

于是设f[x]表示x的子树中最多能选出多少条长度不小于mid的互不相交的链,g[x]表示x的子树中,在已选出f[x]条合法链后,最多能从根往下延伸出多长的链(这条链不与已选出的f[x]条链相交,它将和x子树之外的点连接成为合法链)。

我们有结论:一个子树内,必定是先让合法链的个数最多(即最大化f),在此基础上再尽量让从根伸出的链最长(即最大化g)。

考虑转移,首先f[x]+=f[son],然后合并子树伸出的链。问题转化为找尽量多的不重复数对,是两两和不小于mid,且最后剩下来的最大的数最大。

先将所有数从小到大排序,然后从小到大考虑(若某个数已经配对则跳过),对于一个数x,在后面找到最小的数y使x+y>=mid,再找y以及y之后的第一个尚未配对的数x与之配对,f++,若不存在则x不与任何数配对,用x更新g。

可以证明,这个贪心策略一定是最优的,f和g在此策略下都能被最大化。

至于找y可以直接二分或单调指针扫描,找y后第一个可用的数可以用set或经典的并查集处理。

期望得分:100

复杂度:$O(n\log^2 n)$或$O(n\log n)$

 #include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
using namespace std; const int N=;
int n,m,u,v,w,cnt,L,R,mid,ans,f[N],g[N],a[N],fa[N];
int h[N],to[N<<],nxt[N<<],val[N<<];
void add(int u,int v,int w){ to[++cnt]=v; val[cnt]=w; nxt[cnt]=h[u]; h[u]=cnt; }
int get(int x){ return (fa[x]==x) ? x : fa[x]=get(fa[x]); } void DP(int x,int Fa){
f[x]=; g[x]=; int tot=;
For(i,x) if ((k=to[i])!=Fa) DP(k,x),f[x]+=f[k];
For(i,x) if ((k=to[i])!=Fa) a[++tot]=g[k]+val[i];
if (!tot) return;
sort(a+,a+tot+);
while (tot && a[tot]>=mid) tot--,f[x]++;
if (!tot) return;
rep(i,,tot+) fa[i]=i;
rep(i,,tot-){
if (fa[i]!=i) continue;
int r=lower_bound(a+i+,a+tot+,mid-a[i])-a;
if (r>tot || a[i]+a[r]<mid) continue;
int t=get(r);
if (t>tot) continue;
f[x]++; fa[i]=get(i+); fa[t]=get(t+);
}
rep(i,,tot) if (fa[i]==i) g[x]=a[i];
} int main(){
freopen("track.in","r",stdin);
freopen("track.out","w",stdout);
scanf("%d%d",&n,&m);
rep(i,,n) scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w),R+=w;
while (L<=R){
mid=(L+R)>>; DP(,);
if (f[]>=m) ans=mid,L=mid+; else R=mid-;
}
printf("%d\n",ans);
return ;
}

track

D2T1:旅行

首先树的情况不难想到贪心策略,由于走的是一个dfs序,所以每次选最小的相邻的尚未走过的点走过去即可。

n=m的情况是一个环套树,由于最终的遍历过程中一定有一条边没有被遍历到,那么枚举这条边,删掉后再跑树的情况即可。

注意如果删的边不在环上会导致图不连通,此时显然得到的遍历序列长度小于n,判掉就好。

但这样n=m写的太丑的话可能会被卡常,所以最好在走跑dfs的时候实时判断是否比当前字典序是否比最优解大,若是则直接退出。

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=;
int n,m,d,tot,cnt,res[N],tmp[N],ans[N],h[N],to[N<<],nxt[N<<],E[N][N];
bool fl,fl1,vis[N];
struct edg{ int u,v; }e[N];
void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } void dfs(int x){
if (fl) return;
res[++tot]=x; vis[x]=;
if (res[tot]<ans[tot]) fl1=;
if (res[tot]>ans[tot] && !fl1) { fl=; return; }
for (int i=h[x]; i; i=nxt[i]){
int k=to[i];
if (!((x==e[d].u && k==e[d].v) || (x==e[d].v && k==e[d].u)) && !vis[k]) dfs(k);
}
} int main(){
freopen("travel.in","r",stdin);
freopen("travel.out","w",stdout);
scanf("%d%d",&n,&m);
rep(i,,n) ans[i]=n+;
rep(i,,m){
scanf("%d%d",&e[i].u,&e[i].v);
E[e[i].u][++tmp[e[i].u]]=e[i].v;
E[e[i].v][++tmp[e[i].v]]=e[i].u;
}
rep(i,,n){
if (!tmp[i]) continue;
sort(E[i]+,E[i]+tmp[i]+);
for (int j=tmp[i]; j; j--) add(i,E[i][j]);
}
if (m==n-){
dfs();
rep(i,,n) printf("%d ",res[i]);
return ;
}
for (d=; d<=m; d++){
tot=; rep(i,,n) vis[i]=;
fl=; fl1=; dfs();
if (tot<n || fl) continue;
rep(i,,n) ans[i]=res[i];
}
rep(i,,n) printf("%d ",ans[i]);
return ;
}

travel_1

或者直接找到环,只删环上的边。这在环很小的时候效果极佳。

 #include<vector>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
using namespace std; const int N=;
int n,m,u,v,cnt,tim,tot,top,res[N],ans[N];
int cir[N],vis[N],pre[N],h[N],to[N<<],nxt[N<<];
vector<int>a[N];
void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } void dfs(int x){
vis[x]=; res[++top]=x;
For(i,x) if (!vis[k=to[i]]) dfs(k);
} void find(int x){
vis[x]=++tim;
For(i,x) if (!vis[k=to[i]]) pre[k]=x,find(k);
else if (vis[k]>vis[x]){
for (int j=k; j!=x; j=pre[j]) cir[++tot]=j;
cir[++tot]=x;
}
} void dfs2(int x,int fa){
vis[x]=; res[++top]=x;
For(i,x) if ((k=to[i])!=fa && !((x==u && k==v) || (x==v && k==u))) dfs2(k,x);
} int main(){
freopen("travel.in","r",stdin);
freopen("travel.out","w",stdout);
scanf("%d%d",&n,&m);
rep(i,,m) scanf("%d%d",&u,&v),a[u].push_back(v),a[v].push_back(u);
rep(i,,n){
sort(a[i].begin(),a[i].end());
for (int j=a[i].size()-; j>=; j--) add(i,a[i][j]);
}
if (m<n){ dfs(); rep(i,,n) printf("%d ",res[i]); return ; }
find();
rep(i,,n) ans[i]=n+;
rep(i,,tot){
top=; rep(j,,n) vis[j]=;
u=cir[i]; v=cir[i%tot+];
dfs2(,); bool flag=;
rep(j,,n) if (res[j]!=ans[j]){ flag=res[j]<ans[j]; break; }
if (flag) rep(j,,n) ans[j]=res[j];
}
rep(i,,n) printf("%d ",ans[i]);
return ;
}

travel_2

D2T2:填数游戏

算法一:n,m<=3可以直接手算。

m=1时:$2^n$

n=1时:$2^m$

n=2,m=2:样例

n=2,m=3:手算得36

n=3,m=2:手算得36

n=3,m=3:样例

期望得分:20

复杂度:$O(1)$

开始理性分析下题目的性质:

题意是,对于任何一个点(x,y),从(x-1,y)出发的任意一条路径均不大于从(x,y-1)出发的任意一条路径。

那么有两个结论:

1.不存在$w_{x-1,y}=1$,$w_{x,y-1}=0$的情况。即任何一个对角线都是从左下到右上先1后0(或全0全1)。

2.若存在x,y使得$w_{x-1,y}=w_{x,y-1}$,那么(x,y)右下方的所有对角线上每个数都相等,及所有离(x,y)的曼哈顿距离相等的数都相等。

结论一显然。

若存在某个点(x1,y1)和(x2,y2) (x1,x2>=x0,y1,y2>=y0)使得$w_{x1,y1}=1$,$w_{x2,y2}=0$且这两个点到(x0,y0)的曼哈顿距离相等,那么可以从(x-1,y)出发走到(x1,y1),从(x,y-1)出发走到(x2,y2),那么前者路径的字典序一定会大于后者。

结论二得证。

这样证明了两个结论的必要性,理性分析一下可以发现,两个结论合起来就是充要条件。证明大概是讨论多种情况,这里不再叙述。

于是我们有了:

算法二:n=2可以忽视结论2,其中(1,1)和(n,m)可以随意选择,其它每对数有(0,0),(1,0),(1,1)三种取法(限于结论一不能取(0,1))。

很容易得出答案为$4*3^{m-1}$。或者状压DP也行,这里只要压两位所以几乎相当于普通DP。

期望得分:30

复杂度:$O(\log m)$

以上部分分通过数据分治可以得到50分。

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=,mod=1e9+;
int n,m,f[N][]; int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&n,&m);
if (n== || m==){ printf("%d\n",<<(n*m)); return ; }
if (n== && m==){ puts(""); return ; }
if (n== && m==){ puts(""); return ; }
if (n== && m==){ puts(""); return ; }
f[][]=f[][]=f[][]=f[][]=;
rep(i,,m){
f[i][]=f[i][]=((f[i-][]+f[i-][])%mod+(f[i-][]+f[i-][])%mod)%mod;
f[i][]=f[i][]=(f[i-][]+f[i-][])%mod;
}
printf("%d\n",((f[m][]+f[m][])%mod+(f[m][]+f[m][])%mod)%mod);
return ;
}

game(50分)

算法三:n=3时,结论二只会限制2,3行的一些数对相等,考虑状压DP解决。

f[i][S][0/1]表示,考虑到第i列,第i列的状态为S (0<=S<8),是/否存在结论二限制 的方案数。转移考虑这一列和上一列是否会构成限制。

期望得分:15

复杂度:$O(64m)$

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=,mod=1e9+;
int n,m,ans,f[N][][]; int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&n,&m); int ed=(<<n)-;
rep(S,,ed) f[][S][]=;
rep(i,,m-){
rep(S,,ed) if (f[i][S][] || f[i][S][]){
rep(S1,,ed){
if ((S1& && !(S&)) || ((S1&) && !(S&))) continue;
if ((S1& && S&) || (!(S1&) && !(S&))) f[i+][S1][]=(f[i+][S1][]+f[i][S][])%mod;
else f[i+][S1][]=(f[i+][S1][]+f[i][S][])%mod;
if (!(S1&) && (S&)) continue;
f[i+][S1][]=(f[i+][S1][]+f[i][S][])%mod;
}
}
}
rep(S,,ed) ans=(ans+(f[m][S][]+f[m][S][])%mod)%mod;
printf("%d\n",ans);
return ;
}

game(DP)

算法四:根据结论一和结论二给暴搜剪枝,可以在15s内轻松打完8*8的表。

期望得分:15

复杂度:打表后$O(1)$

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=,mod=1e9+;
int n,m,ans,tot,a[N][N],d[N];
struct P{ int x,y; }p[N];
void dfs(int x,int y); void Do(int x,int y){
if (x> && y<m && a[x][y]==a[x-][y+]) p[++tot]=(P){x,y+};
dfs(x,y+);
if (x> && y<m && a[x][y]==a[x-][y+]) tot--;
} void dfs(int x,int y){
if (x>n){ ans++; return; }
if (y>m) { dfs(x+,); return; }
rep(i,,tot) if (p[i].x<x && p[i].y<=y && y<m){
if (~a[x][y] && a[x][y]!=a[x-][y+]) { a[x][y]=-; return; }
a[x][y]=a[x-][y+];
}
if (~a[x][y]){ Do(x,y); a[x][y]=-; return; }
a[x][y]=; Do(x,y); a[x][y]=;
if (!(x> && y<m && a[x][y]<a[x-][y+])) Do(x,y);
a[x][y]=-;
} int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
for (n=; n<=; n++){
for (m=; m<=; m++){
rep(i,,n) rep(j,,m) a[i][j]=-;
ans=; dfs(,); printf("%d ",ans);
}
puts("");
}
return ;
}

game(打表)

以上部分分通过数据分治可以得到80分。

算法五:稍微把表打大一点,暴力找规律即可。不会证明,没什么好说的。

数据范围完全不知道出于什么意义。

有公式ans[n][n]=ans[n-1][n-1]*8-5*2^n,所以事实上出得多大都是没有问题的。

期望得分:100

复杂度:$O(\log n+\log m)$

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int mod=1e9+;
const int d[]={,,,,,,,,};
int n,m; int ksm(int a,int b){
int res=;
for (; b; a=1ll*a*a%mod,b>>=)
if (b & ) res=1ll*res*a%mod;
return res;
} int calc(int n,int m){
if (n==m) return d[n];
if (n==) return ksm(,m);
if (n==) return 4ll*ksm(,m-)%mod;
if (n==) return 112ll*ksm(,m-)%mod;
return ((3ll*d[n]-3ll*ksm(,n))*ksm(,m-n-)%mod+mod)%mod;
} int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&n,&m);
if (n>m) swap(n,m);
printf("%d\n",calc(n,m));
return ;
}

game(100)

D2T3:保卫王国

难度和知识点几乎脱离NOIP范围的一道题,有倍增做法但感觉难度很大,这里直讲动态DP做法。

算法一:n,m<=2000就是经典的《没有上司的舞会》,每次修改暴力重做一次树形DP即可。

期望得分:50

复杂度:$O(n^2)$

算法二:A1,A2保证是链,且修改只涉及到一个位置或两个相邻位置,记录前缀后缀即可。

期望得分:20

复杂度:$O(n)$

算法三:B1深度不超过100,可以发现每次修改只会涉及到它到根的链上的DP值,于是可以优化复杂度。

期望得分:8

复杂度:$O(100n)$

以上部分分通过数据分治可以获得72分。

 #include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int N=1e5+;
int n,m,tp[N],p[N],fa[N];
ll f[N][],g[N][];
vector<int>G[N];
char typ[];
void dp(int u,int ff)
{
f[u][]=;f[u][]=p[u];
for(int i=;i<(int)G[u].size();i++)
if(G[u][i]!=ff)
{
dp(G[u][i],u);
f[u][]+=f[G[u][i]][];
if(f[u][]>1e17)f[u][]=1e17;
f[u][]+=min(f[G[u][i]][],f[G[u][i]][]);
if(f[u][]>1e17)f[u][]=1e17;
}
if(!tp[u])f[u][]=1e17;
if(tp[u]==)f[u][]=1e17;
}
void dfs0(int u)
{
for(int i=;i<(int)G[u].size();i++)
if(G[u][i]!=fa[u])fa[G[u][i]]=u,dfs0(G[u][i]);
}
void modify(int u,int son)
{
g[u][]=f[u][],g[u][]=f[u][];
if(g[u][]!=1e17)
{
g[u][]-=f[son][];
g[u][]+=g[son][];
if(g[u][]>1e17)g[u][]=1e17;
}
if(g[u][]!=1e17)
{
g[u][]-=min(f[son][],f[son][]);
g[u][]+=min(g[son][],g[son][]);
if(g[u][]>1e17)g[u][]=1e17;
}
if(u!=)modify(fa[u],u);
}
int main()
{
scanf("%d%d%s",&n,&m,typ);
for(int i=;i<=n;i++)scanf("%d",&p[i]);
if(n<=&&m<=)
{
memset(tp,-,sizeof tp);
for(int i=,x,y;i<n;i++)scanf("%d%d",&x,&y),G[x].push_back(y),G[y].push_back(x);
while(m--)
{
int a,x,b,y;
scanf("%d%d%d%d",&a,&x,&b,&y);
tp[a]=x,tp[b]=y;
dp(,);
tp[a]=tp[b]=-;
ll ans=min(f[][],f[][]);
if(ans==1e17)puts("-1");
else printf("%lld\n",ans);
}
return ;
}
if((typ[]=='B'||typ[]=='C')&&typ[]=='')
{
for(int i=,x,y;i<n;i++)scanf("%d%d",&x,&y),G[x].push_back(y),G[y].push_back(x);
dfs0();
memset(tp,-,sizeof tp);
dp(,);
while(m--)
{
int b,y;
scanf("%*d%*d%d%d",&b,&y);
g[b][y]=f[b][y];g[b][y^]=1e17;
if(b!=)modify(fa[b],b);
if(g[][]==1e17)puts("-1");
else printf("%lld\n",g[][]);
}
return ;
}
if(typ[]=='A')for(int i=;i<n;i++)scanf("%*d%*d");
if(typ[]=='A'&&typ[]=='')
{
f[n][]=,f[n][]=p[n];
for(int i=n-;i;i--)f[i][]=f[i+][],f[i][]=min(f[i+][],f[i+][])+p[i];
g[][]=1e17,g[][]=p[];
for(int i=;i<=n;i++)g[i][]=g[i-][],g[i][]=min(g[i-][],g[i-][])+p[i];
while(m--)
{
int b,y;scanf("%*d%*d%d%d",&b,&y);
ll ans;
if(!y)ans=f[b+][]+g[b-][];
else ans=min(f[b+][],f[b+][])+min(g[b-][],g[b-][])+p[b];
printf("%lld\n",ans);
}
return ;
}
if(typ[]=='A'&&typ[]=='')
{
f[n][]=,f[n][]=p[n];
for(int i=n-;i;i--)f[i][]=f[i+][],f[i][]=min(f[i+][],f[i+][])+p[i];
g[][]=,g[][]=p[];
for(int i=;i<=n;i++)g[i][]=g[i-][],g[i][]=min(g[i-][],g[i-][])+p[i];
while(m--)
{
int a,x,b,y;scanf("%d%d%d%d",&a,&x,&b,&y);
if(a>b)swap(a,b),swap(x,y);
if(!x&&!y){puts("-1");continue;}
ll ans=f[b][y]+g[a][x];
printf("%lld\n",ans);
}
return ;
}
while(m--)puts("-1");
return ;
}

72分代码(From CTF)

算法四:树上带修改最小权覆盖问题。最小权覆盖=全集-最大权独立集。通过设inf和-inf控制选与不选。

带修改最大权独立集问题有经典的动态DP做法,通过链分治、矩乘和线段树加速。

树剖$O(n\log^2 n)$且常数极大,理论上极限数据可以卡到20s以上。

使用LCT或全局平衡二叉树可以做到$O(n\log n)$。

期望得分:100

复杂度:$O(n\log n)$

 #include<cstdio>
#include<cstring>
#include<algorithm>
#define ls (x<<1)
#define rs (ls|1)
#define lson ls,L,mid
#define rson rs,mid+1,R
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
typedef long long ll;
using namespace std; const int N=;
const ll inf=1e12;
ll sm,a[N],f[N][];
int n,m,u,v,s1,s2,q[N],top[N],L[N],R[N],id[N],son[N],sz[N],fa[N];
int cnt,tot,tim,h[N],to[N<<],nxt[N<<];
inline void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } struct Mat{
ll g[][];
Mat(){ g[][]=g[][]=g[][]=g[][]=; }
}val[N],T[N<<]; Mat operator *(const Mat &a,const Mat &b){
Mat c;
rep(i,,) rep(j,,) rep(k,,) c.g[i][k]=max(c.g[i][k],a.g[i][j]+b.g[j][k]);
return c;
} void dfs(int x){
sz[x]=; f[x][]=a[x];
For(i,x) if ((k=to[i])!=fa[x]){
fa[k]=x; dfs(k); sz[x]+=sz[k];
f[x][]+=max(f[k][],f[k][]); f[x][]+=f[k][];
if (sz[k]>sz[son[x]]) son[x]=k;
}
ll g0=,g1=a[x];
For(i,x) if ((k=to[i])!=fa[x] && k!=son[x]) g0+=max(f[k][],f[k][]),g1+=f[k][];
val[x].g[][]=val[x].g[][]=g0; val[x].g[][]=g1;
} void build(int x,int L,int R){
if (L==R){ T[x]=val[id[L]]; return; }
int mid=(L+R)>>;
build(lson); build(rson);
T[x]=T[ls]*T[rs];
} void mdf(int x,int L,int R,int pos){
if (L==R){ T[x]=val[id[L]]; return; }
int mid=(L+R)>>;
if (pos<=mid) mdf(lson,pos); else mdf(rson,pos);
T[x]=T[ls]*T[rs];
} Mat que(int x,int L,int R,int l,int r){
if (L==l && r==R) return T[x];
int mid=(L+R)>>;
if (r<=mid) return que(lson,l,r);
else if (l>mid) return que(rson,l,r);
else return que(lson,l,mid)*que(rson,mid+,r);
} void solve(int x,ll k){
sm+=k-a[x]; val[x].g[][]+=k-a[x]; a[x]=k;
Mat od,nw;
while (x){
od=que(,,n,L[top[x]],R[top[x]]); mdf(,,n,L[x]);
nw=que(,,n,L[top[x]],R[top[x]]); x=fa[top[x]];
val[x].g[][]=val[x].g[][]+=max(nw.g[][],nw.g[][])-max(od.g[][],od.g[][]);
val[x].g[][]+=nw.g[][]-od.g[][];
}
} int main(){
freopen("defense.in","r",stdin);
freopen("defense.out","w",stdout);
scanf("%d%d%*s",&n,&m); q[]=; tot=;
rep(i,,n) scanf("%lld",&a[i]),sm+=a[i];
rep(i,,n) scanf("%d%d",&u,&v),add(u,v),add(v,u);
dfs();
rep(x,,n) For(i,x) if ((k=to[i])!=fa[x]) q[++tot]=k;
rep(i,,n) if (!top[u=q[i]]){
for (int k=u; k; k=son[k]) id[L[k]=++tim]=k,top[k]=u;
R[u]=tim;
}
build(,,n);
rep(i,,m){
scanf("%d%d%d%d",&u,&s1,&v,&s2); int t1=a[u],t2=a[v];
if (!s1 && !s2 && (fa[u]==v || fa[v]==u)){ puts("-1"); continue; }
if (s1) solve(u,a[u]-inf); else solve(u,a[u]+inf);
if (s2) solve(v,a[v]-inf); else solve(v,a[v]+inf);
Mat ans=que(,,n,L[],R[]); ll res=sm-max(ans.g[][],ans.g[][]);
res=(res%inf+inf)%inf; printf("%lld\n",res);
solve(u,t1); solve(v,t2);
}
return ;
}

defense(树剖)

 #include<cstdio>
#include<cstring>
#include<algorithm>
#define ls (x<<1)
#define rs (ls|1)
#define lson ls,L,mid
#define rson rs,mid+1,R
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
typedef long long ll;
using namespace std; const int N=;
const ll inf=1e12;
ll sm,a[N],s[N][];
int n,m,u,v,s1,s2,top[N],L[N],R[N],id[N],son[N],sz[N],f[N],fa[N],ch[N][];
int cnt,h[N],to[N<<],nxt[N<<];
inline void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } struct Mat{
ll g[][];
Mat(){ g[][]=g[][]=g[][]=g[][]=; }
}val[N],T[N]; Mat operator *(const Mat &a,const Mat &b){
Mat c;
rep(i,,) rep(j,,) rep(k,,) c.g[i][k]=max(c.g[i][k],a.g[i][j]+b.g[j][k]);
return c;
} void dfs(int x){
s[x][]=a[x];
For(i,x) if ((k=to[i])!=f[x]){
fa[k]=x; f[k]=x; dfs(k);
s[x][]+=max(s[k][],s[k][]); s[x][]+=s[k][];
}
ll g0=,g1=a[x];
For(i,x) if ((k=to[i])!=f[x] && k!=son[x]) g0+=max(s[k][],s[k][]),g1+=s[k][];
val[x].g[][]=val[x].g[][]=g0; val[x].g[][]=g1; T[x]=val[x];
} bool isroot(int x){ return (!f[x]) || (ch[f[x]][]!=x && ch[f[x]][]!=x); } void upd(int x){
if (ch[x][]) T[x]=T[ch[x][]]*val[x]; else T[x]=val[x];
if (ch[x][]) T[x]=T[x]*T[ch[x][]];
} void rot(int x){
int y=f[x],z=f[y],w=(ch[y][]==x);
if (!isroot(y)) ch[z][ch[z][]==y]=x;
f[x]=z; f[y]=x; f[ch[x][w^]]=y;
ch[y][w]=ch[x][w^]; ch[x][w^]=y; upd(y);
} void splay(int x){
while (!isroot(x)){
int y=f[x],z=f[y];
if (!isroot(y)) ((ch[z][]==y)^(ch[y][]==x))?rot(x):rot(y);
rot(x);
}
upd(x);
} void access(int x){
for (int y=; x; y=x,x=f[x]){
splay(x); Mat od,nw;
if (ch[x][]) nw=T[ch[x][]];
ch[x][]=y;
if (ch[x][]) od=T[ch[x][]];
val[x].g[][]=val[x].g[][]+=max(nw.g[][],nw.g[][])-max(od.g[][],od.g[][]);
val[x].g[][]+=nw.g[][]-od.g[][]; upd(x);
}
} void solve(int x,ll k){ access(x); splay(x); val[x].g[][]+=k-a[x]; a[x]=k; upd(x); } int main(){
freopen("defense.in","r",stdin);
freopen("defense.out","w",stdout);
scanf("%d%d%*s",&n,&m);
rep(i,,n) scanf("%lld",&a[i]),sm+=a[i];
rep(i,,n) scanf("%d%d",&u,&v),add(u,v),add(v,u);
dfs();
rep(i,,m){
scanf("%d%d%d%d",&u,&s1,&v,&s2); int t1=a[u],t2=a[v];
if (!s1 && !s2 && (fa[u]==v || fa[v]==u)){ puts("-1"); continue; }
if (s1) solve(u,a[u]-inf); else solve(u,a[u]+inf);
if (s2) solve(v,a[v]-inf); else solve(v,a[v]+inf);
splay(); ll res=sm-max(T[].g[][],T[].g[][]);
res=(res%inf+inf)%inf; printf("%lld\n",res);
solve(u,t1); solve(v,t2);
}
return ;
}

defense(LCT)

NOIP2018提高组题解的更多相关文章

  1. NOIP2018 提高组题解

    Day1 T1 据说是原题积木大赛,但是考场上蠢了,只会写数据结构,于是写了一个线段树\(+\)堆\(+\)贪心,先选出最小的,然后区间修改,然后把左右两端区间的最小值丢进堆里,不停从堆中去最小值更新 ...

  2. 【NOIP2018】提高组题解

    [NOIP2018]提高组题解 其实就是把写过的打个包而已 道路铺设 货币系统 赛道修建 旅行 咕咕咕 咕咕咕

  3. NOIP2018提高组省一冲奖班模测训练(六)

    NOIP2018提高组省一冲奖班模测训练(六) https://www.51nod.com/Contest/ContestDescription.html#!#contestId=80 20分钟AC掉 ...

  4. NOIP2018提高组省一冲奖班模测训练(五)

    NOIP2018提高组省一冲奖班模测训练(五) http://www.51nod.com/Contest/ContestDescription.html#!#contestId=79 今天有点浪…… ...

  5. noip2010提高组题解

    NOIP2010提高组题解 T1:机器翻译 题目大意:顺序输入n个数,有一个队列容量为m,遇到未出现元素入队,求入队次数. AC做法:直接开1000的队列模拟过程. T2:乌龟棋 题目大意:有长度为n ...

  6. NOIP 2014 提高组 题解

    NOIP 2014 提高组 题解 No 1. 生活大爆炸版石头剪刀布 http://www.luogu.org/problem/show?pid=1328 这是道大水题,我都在想怎么会有人错了,没算法 ...

  7. NOIP 2001 提高组 题解

    NOIP 2001 提高组 题解 No 1. 一元三次方程求解 https://vijos.org/p/1116 看见有人认真推导了求解公式,然后猥琐暴力过的同学们在一边偷笑~~~ 数据小 暴力枚举即 ...

  8. NOIP 2000 提高组 题解

    NOIP2000 提高组 题解 No 1. 进制转换 https://www.rqnoj.cn/problem/295 水题 对于n和基数r, 每次用n mod r, 把余数按照逆序排列 注意 mod ...

  9. [NOIp2018提高组]旅行

    [NOIp2018提高组]旅行: 题目大意: 一个\(n(n\le5000)\)个点,\(m(m\le n)\)条边的连通图.可以从任意一个点出发,前往任意一个相邻的未访问的结点,或沿着第一次来这个点 ...

随机推荐

  1. 从INT_MAX和INT_MIN看补码

    刷一道题的时候遇到INT_MAX和INT_MIN的问题,有些东西忘了,梳理一下. INT_MAX为2147483647,INT_MIN为-2147483648,为什么MIN的绝对值比MAX多1呢,因为 ...

  2. php菜刀分析学习

    这里以eval为例 我们知道, php中的eval能把字符串当代码执行: eval('phpcode'); 注意, 这里的代码要有分号结尾, 我们测试: 我们创建一个最简单的SHELL: <?p ...

  3. 安装 Google BBR 加速VPS网络

    Google BBR就是谷歌公司提出的一个开源TCP拥塞控制的算法.详情可以看这儿:https://lwn.net/Articles/701165.https://blog.sometimesnaiv ...

  4. phinx:php数据库迁移

    Phinx使你的php app进行数据迁移的过程变得异常轻松,在五分钟之内你就可以安装好Phinx 并进行数据迁移. 特性 使用php代码进行数据迁移 部署模式下迁移 五分钟之内使用 不再担心数据库的 ...

  5. docker简单介绍(资料收集总结)

    [前言] 首先,感谢我的leader总是会问我很多技术的基本资料,让我这个本来对于各种技术只知道操作命令不关注理论知识的人,开始重视理论资料. 关于docker的操作步骤等等,都是之前学习的,现在补上 ...

  6. ActiveMQ-Prefetch机制和constantPendingMessageLimitStrategy

    首先简要介绍一下prefetch机制.ActiveMQ通过prefetch机制来提高性能,这意味这 客户端的内存里可能会缓存一定数量的消息.缓存消息的数量由prefetch limit来控 制.当某个 ...

  7. oracle相关命令收集-张

    orcle相关命令收集 1,用管理员登陆 /as sysdba:2, 更改用户密码 alter user name identified by password: alter user exptest ...

  8. LeetCode解题报告—— Best Time to Buy and Sell Stock

    Best Time to Buy and Sell Stock Say you have an array for which the ith element is the price of a gi ...

  9. 洛谷P1615 西游记公司 题解

    题目传送门 这道题题面写得非常好. 但好像并没有什么ruan用. 这道题貌似就是把时间差求出来,乘上猪八戒能偷的电脑数就好了.(注意long long) #include<bits/stdc++ ...

  10. Base Class 慎用箭头函数

    在项目中,child继承base的时候,需要重新修改base.fun的逻辑,但是有些情况下面并不是简单的覆盖,而是在base.fun的逻辑基础上进行加工处理. 刚开始接触es6的时候也许都遇到过,ch ...