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
  • 相关阅读:
    应用程序发生异常,未知的软件异常0x0eedfade,位置为0x0000001355C
    关于条件表达式的理解
    *p++、*++p、(*p)++、++(*p)的比较
    排序算法01_选择排序
    递归_汉诺塔问题
    排序算法00_冒泡排序
    深入理解C语言
    虚拟机_第一篇---创建Linux虚拟机
    虚拟机_第〇篇---虚拟机的下载安装与功能简介
    对i++与++i的理解
  • 原文地址:https://www.cnblogs.com/HocRiser/p/10416144.html
Copyright © 2011-2022 走看看