长链剖分,类似于重链剖分(dsu on tree)的一种替代算法。最广泛的用法是优化与深度有关的树上DP,以及处理一些与点分治类似的问题。有一部分长链剖分题也可以用dsu on tree做,单复杂度往往会多一个log。
每个点找到高度最大的儿子作为自己的重儿子,连续的重儿子形成重链。可以发现,一个点到根要经过的重链最多为$O(\sqrt{n})$个。一个点的任意祖先所在链长都不小于这个点所在的链长,容易证明,当重儿子信息$O(1)$传递,轻儿子信息$O(轻儿子所在重链长度)$传递时,均摊到每个点的复杂度都是$O(1)$,总复杂度为$O(n)$。
例一:[BZOJ3252]树上“k取方格数”问题,选k个叶子使到根路径并权值和最大。
这里只是用到了长链剖分的思想优化。
考虑网络流,发现树上不存在退流,于是模拟贪心,不断找叶子到根的权值最大的路径,计入答案后将路径清零。
显然每次可以取一条权值最大的长链即可。
1 #include<queue> 2 #include<cstdio> 3 #include<cstring> 4 #include<iostream> 5 #include<algorithm> 6 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 7 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 8 typedef long long ll; 9 using namespace std; 10 11 const int N=200010; 12 ll ans,len[N]; 13 int n,k,u,v,cnt,a[N],son[N],h[N],to[N],nxt[N]; 14 priority_queue<ll>Q; 15 16 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } 17 18 void dfs(int x){ 19 For(i,x){ 20 dfs(k=to[i]); 21 if (len[k]>len[son[x]]) son[x]=k; 22 } 23 len[x]=len[son[x]]+a[x]; 24 } 25 26 void dfs2(int x,int top){ 27 if (x==top) Q.push(len[x]); 28 if (son[x]) dfs2(son[x],top); 29 For(i,x) if ((k=to[i])!=son[x]) dfs2(k,k); 30 } 31 32 int main(){ 33 freopen("bzoj3252.in","r",stdin); 34 freopen("bzoj3252.out","w",stdout); 35 scanf("%d%d",&n,&k); 36 rep(i,1,n) scanf("%d",&a[i]); 37 rep(i,2,n) scanf("%d%d",&u,&v),add(u,v); 38 dfs(1); dfs2(1,1); 39 while (k && !Q.empty()) ans+=Q.top(),Q.pop(),k--; 40 printf("%lld\n",ans); 41 return 0; 42 }
例二:[CF1009F]对每个子树找到一个深度使该子树内该深度的点最多。
f[x][i]表示x的i级子孙个数,发现重儿子的结果可以直接利用,轻儿子可以线性合并。
用指针实现二维数组以节省空间。这是此类问题的经典模板。
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 6 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 7 typedef long long ll; 8 using namespace std; 9 10 const int N=1000010; 11 int n,u,v,len[N],ans[N],fa[N],son[N],tmp[N],*f[N],*id=tmp; 12 int cnt,h[N],nxt[N<<1],to[N<<1]; 13 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } 14 15 void dfs(int x){ 16 For(i,x) if ((k=to[i])!=fa[x]){ 17 fa[k]=x; dfs(k); 18 if (len[k]>len[son[x]]) son[x]=k; 19 } 20 len[x]=len[son[x]]+1; 21 } 22 23 void DP(int x){ 24 f[x][0]=1; 25 if (son[x]) f[son[x]]=f[x]+1,DP(son[x]),ans[x]=ans[son[x]]+1; 26 For(i,x) if ((k=to[i])!=fa[x] && k!=son[x]){ 27 f[k]=id; id+=len[k]; DP(k); 28 rep(j,1,len[k]){ 29 f[x][j]+=f[k][j-1]; 30 if (f[x][j]>f[x][ans[x]] || (f[x][j]==f[x][ans[x]] && j<ans[x])) ans[x]=j; 31 } 32 } 33 if (f[x][ans[x]]==1) ans[x]=0; 34 } 35 36 int main(){ 37 freopen("1009F.in","r",stdin); 38 freopen("1009F.out","w",stdout); 39 scanf("%d",&n); 40 rep(i,2,n) scanf("%d%d",&u,&v),add(u,v),add(v,u); 41 dfs(1); f[1]=id; id+=len[1]; DP(1); 42 rep(i,1,n) printf("%d\n",ans[i]); 43 return 0; 44 }
例三:[COGS2652]树上每个点有两个权值ai,bi,找一条长为m的路径使sum(a[i])/sum(b[i])最小。
分数规划后变成找一条长度为m的最小路径,同样f[x][i]表示x开始往下的长为i的链的最小权值和为多少。
转移与更新答案显然,注意由于重儿子转移过来有一个全部加a[x]的操作,于是给每个点记录一个增量就好了。
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 6 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 7 typedef long long ll; 8 using namespace std; 9 10 const int N=200010; 11 int n,m,u,v,len[N],fa[N],son[N],a[N],b[N]; 12 double val[N],tmp[N],*f[N],*id=tmp,ans=1e18; 13 int cnt,h[N],nxt[N<<1],to[N<<1]; 14 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } 15 16 void dfs(int x){ 17 For(i,x) if ((k=to[i])!=fa[x]){ 18 fa[k]=x; dfs(k); 19 if (len[k]>len[son[x]]) son[x]=k; 20 } 21 len[x]=len[son[x]]+1; 22 } 23 24 void DP(int x,double mid){ 25 val[x]=a[x]-mid*b[x]; f[x][0]=0; 26 if (son[x]) f[son[x]]=f[x]+1,DP(son[x],mid),val[x]+=val[son[x]],f[x][0]-=val[son[x]]; 27 For(i,x) if ((k=to[i])!=fa[x] && k!=son[x]){ 28 f[k]=id; id+=len[k]; DP(k,mid); 29 rep(j,0,min(len[k]-1,m-1)) 30 if (m-j-1<len[x]) ans=min(ans,f[k][j]+val[k]+f[x][m-j-1]+val[x]); 31 rep(j,0,min(len[k]-1,m-1)) 32 f[x][j+1]=min(f[x][j+1],f[k][j]+val[k]-val[x]+a[x]-mid*b[x]); 33 } 34 if (m<len[x]) ans=min(ans,f[x][m]+val[x]); 35 } 36 37 int main(){ 38 freopen("cogs2652.in","r",stdin); 39 freopen("cogs2652.out","w",stdout); 40 scanf("%d%d",&n,&m); m--; 41 rep(i,1,n) scanf("%d",&a[i]); 42 rep(i,1,n) scanf("%d",&b[i]); 43 rep(i,1,n) ans=min(ans,1.*a[i]/b[i]); 44 if (m==-2 || !m){ printf("%.2lf\n",ans); return 0; } 45 rep(i,2,n) scanf("%d%d",&u,&v),add(u,v),add(v,u); 46 dfs(1); double l=0,r=N; 47 while (r-l>1e-3){ 48 double mid=(l+r)/2; 49 memset(tmp,0x7f,sizeof(tmp)); ans=1e18; 50 id=tmp; f[1]=id; id+=len[1]; DP(1,mid); 51 if (ans>=0) l=mid; else r=mid; 52 } 53 if (l>=200000) puts("-1"); else printf("%.2lf\n",l); 54 return 0; 55 }
例四:[BZOJ4543]一棵树中选3个点,两两距离相等,求方案数。
暴力DP方法加上长链剖分即可。
https://www.cnblogs.com/zhoushuyu/p/9468669.html
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 6 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 7 typedef long long ll; 8 using namespace std; 9 10 const int N=100010; 11 ll ans; 12 int n,u,v,len[N],fa[N],son[N],tmp[N<<2],*f[N],*g[N],*id=tmp; 13 int cnt,h[N],nxt[N<<1],to[N<<1]; 14 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } 15 16 void dfs(int x){ 17 For(i,x) if ((k=to[i])!=fa[x]){ 18 fa[k]=x; dfs(k); 19 if (len[k]>len[son[x]]) son[x]=k; 20 } 21 len[x]=len[son[x]]+1; 22 } 23 24 void DP(int x){ 25 if (son[x]) f[son[x]]=f[x]+1,g[son[x]]=g[x]-1,DP(son[x]); 26 f[x][0]=1; ans+=g[x][0]; 27 For(i,x) if ((k=to[i])!=fa[x] && k!=son[x]){ 28 f[k]=id; id+=len[k]<<1; g[k]=id; id+=len[k]<<1; DP(k); 29 rep(j,0,len[k]-1){ 30 if (j) ans+=f[x][j-1]*g[k][j]; 31 ans+=g[x][j+1]*f[k][j]; 32 } 33 rep(j,0,len[k]-1){ 34 g[x][j+1]+=f[x][j+1]*f[k][j]; 35 if (j) g[x][j-1]+=g[k][j]; 36 f[x][j+1]+=f[k][j]; 37 } 38 } 39 } 40 41 int main(){ 42 freopen("bzoj4543.in","r",stdin); 43 freopen("bzoj4543.out","w",stdout); 44 scanf("%d",&n); 45 rep(i,2,n) scanf("%d%d",&u,&v),add(u,v),add(v,u); 46 dfs(1); f[1]=id; id+=len[1]<<1; g[1]=id; id+=len[1]<<1; 47 DP(1); printf("%lld\n",ans); 48 return 0; 49 }
例五:[BZOJ3653]谈笑风生
同样没有什么要说的,暴力统计以深度为下标的信息,用上长链剖分复杂度就有保证了。
注意我们长链剖分的时候无法记录前缀和,但可以记录后缀和,且后缀和也是可以DP直接转移的。
1 #include<cstdio> 2 #include<vector> 3 #include<cstring> 4 #include<iostream> 5 #include<algorithm> 6 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 7 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 8 typedef long long ll; 9 using namespace std; 10 11 const int N=300010; 12 int n,u,v,Q,dep[N],fa[N],son[N]; 13 int cnt,len[N],sz[N],h[N],nxt[N<<1],to[N<<1]; 14 ll ans[N],tmp[N],*f[N],*id=tmp; 15 struct P{ int x,y; }; 16 vector<P>ve[N]; 17 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } 18 19 void dfs(int x){ 20 dep[x]=dep[fa[x]]+1; sz[x]=1; 21 For(i,x) if ((k=to[i])!=fa[x]){ 22 fa[k]=x; dfs(k); sz[x]+=sz[k]; 23 if (len[k]>len[son[x]]) son[x]=k; 24 } 25 len[x]=len[son[x]]+1; 26 } 27 28 void DP(int x){ 29 if (son[x]) f[son[x]]=f[x]+1,DP(son[x]),f[x][0]+=f[son[x]][0]; 30 f[x][0]+=sz[x]-1; 31 For(i,x) if ((k=to[i])!=fa[x] && k!=son[x]){ 32 f[k]=id; id+=len[k]; DP(k); 33 rep(j,0,len[k]-1) f[x][j+1]+=f[k][j]; 34 f[x][0]+=f[k][0]; 35 } 36 int ed=ve[x].size()-1; 37 rep(i,0,ed){ 38 int k=ve[x][i].y,id=ve[x][i].x; 39 ans[id]+=1ll*(sz[x]-1)*min(dep[x]-1,k); 40 if (k>=len[x]-1) ans[id]+=f[x][0]-sz[x]+1; 41 else ans[id]+=f[x][0]-sz[x]+1-f[x][k+1]; 42 } 43 } 44 45 int main(){ 46 freopen("bzoj3653.in","r",stdin); 47 freopen("bzoj3653.out","w",stdout); 48 scanf("%d%d",&n,&Q); 49 rep(i,2,n) scanf("%d%d",&u,&v),add(u,v),add(v,u); 50 dfs(1); 51 rep(i,1,Q) scanf("%d%d",&u,&v),ve[u].push_back((P){i,v}); 52 f[1]=id; id+=len[1]; DP(1); 53 rep(i,1,Q) printf("%lld\n",ans[i]); 54 return 0; 55 }