zoukankan      html  css  js  c++  java
  • 长链剖分题表

    长链剖分,类似于重链剖分(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
    ",ans);
    41     return 0;
    42 }
    BZOJ3252

    例二:[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
    ",ans[i]);
    43     return 0;
    44 }
    CF1009F

     例三:[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
    ",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
    ",l);
    54     return 0;
    55 }
    COGS2652

    例四:[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
    ",ans);
    48     return 0;
    49 }
    BZOJ4543

    例五:[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
    ",ans[i]);
    54     return 0;
    55 }
    BZOJ3653
  • 相关阅读:
    51 Nod 1086 多重背包问题(单调队列优化)
    51 Nod 1086 多重背包问题(二进制优化)
    51 Nod 1085 01背包问题
    poj 2559 Largest Rectangle(单调栈)
    51 Nod 1089 最长回文子串(Manacher算法)
    51 Nod N的阶乘的长度 (斯特林近似)
    51 Nod 1134 最长递增子序列(经典问题回顾)
    51 Nod 1020 逆序排列
    PCA-主成分分析(Principal components analysis)
    Python中cPickle
  • 原文地址:https://www.cnblogs.com/HocRiser/p/10416144.html
Copyright © 2011-2022 走看看