zoukankan      html  css  js  c++  java
  • 长链剖分优化dp三例题

      首先,重链剖分我们有所认识,在dsu on tree和数据结构维护链时我们都用过他的性质。

      在这里,我们要介绍一种新的剖分方式,我们求出这个点到子树中的最长链长,这个链长最终从哪个儿子更新而来,那个儿子就是所谓的“重儿子”,也可以叫长儿子。

      我们的做法就是,在统计一个点的信息时,对于重儿子,我们直O(1)接继承它的答案(这里有指针技巧,只能看代码,不可言传),对于轻儿子我们暴力统计。

      复杂度分析:一个点被计算,最多只会在作为重链上的点时被继承一次,在重链顶端时被暴力统计一次。所以最终复杂度是O(N)的。

      因为我们这里要谈的是dp优化,所以我们还没有必要研究这个结构的性质。

      它有两个应用,首先就是优化以链长度为下标的树形dp,也就是今天我们要谈的玩法,还有一个是快速求一个点的k级祖先,这个我们先不研究。

      只凭语言大家很难体会到这个算法的难度,下面我们看一些题目。

    首先是CF1009:

      这道题完全可以用dsu on tree的科技过去,但是为了能入手一道简单的长剖题目,我们还是思考一下。

      如果设计一个dp:dp[i][j]表示以i为根的子树内离i距离为j的节点个数。转移方程也就很好写了:dp[x][j]+=dp[y][j-1]。(y是x的儿子),我们观察,在继承一个儿子的答案时,儿子的数组整体左移一个元素的位置可以直接贡献给父亲,于是我们就做到了O(1)继承。

      于是暴力统计其他儿子的时候我们直接按方程转移即可。

     代码:

     1 //倔强芬芳了惘然 
     2 #pragma GCC optimize(3)
     3 #include<bits/stdc++.h>
     4 using namespace std;
     5 const int N=1000005;
     6 struct node{int y,nxt;}e[N*2];
     7 int n,m,a[N],d[N],fa[N],son[N],h[N];
     8 int ans[N],cnt[N],c,st[N],tt;
     9 void add(int x,int y){
    10     e[++c]=(node){y,h[x]};h[x]=c;
    11     e[++c]=(node){x,h[y]};h[y]=c;
    12 } void dfs(int x){ d[x]=1;
    13     for(int i=h[x],y;i;i=e[i].nxt)
    14     if((y=e[i].y)!=fa[x]){
    15         fa[y]=x;dfs(y);d[x]=max(d[x],d[y]+1);
    16         if(d[y]>d[son[x]]) son[x]=y;
    17     } return ;
    18 } void solve(int x){
    19     int *f=&cnt[st[x]=++tt],*g;
    20     f[ans[x]=0]=1;
    21     if(son[x]) solve(son[x]),
    22     ans[x]=ans[son[x]]+1;else return ;
    23     if(ans[x]==1) ans[x]=0;
    24     for(int i=h[x],y;i;i=e[i].nxt)
    25     if((y=e[i].y)!=fa[x]&&y!=son[x]){
    26         solve(y);g=&cnt[st[y]];
    27         for(int j=0;j<=d[y]-1;j++) 
    28           if((f[j+1]+=g[j])>=f[ans[x]]&&j+1<ans[x]||
    29         f[j+1]>f[ans[x]]) ans[x]=j+1;
    30     } return ;
    31 } void solve(){
    32     dfs(1);solve(1);
    33     for(int i=1;i<=n;i++)
    34     printf("%d
    ",ans[i]);
    35 } int main(){
    36     scanf("%d",&n);
    37     for(int i=1,x,y;i<n;i++)
    38     scanf("%d%d",&x,&y),add(x,y);
    39     solve();return 0;
    40 }
    长链剖分

    现在是POI2014Hotels

      其实大部分人对计数题还是有一定抵触的,因为一些做法的正确性很难把握。dp是很常用的计数手段,但是这个题的dp方程很有意思。向各位推荐一篇题解→luogu题解1

      我们只借用它的方程考虑这个能不能直接O(1)继承重儿子的答案?(当然可以啦)

      但是我们注意,f数组和g数组在继承的时候方向是不一样的,因为这一点,我们最好在递归之前就为下面的计算分配好指针,来保证顺利继承,另外,在空间分配上,这个题也很巧妙。因为我们在长链上,f数组不断向后偏移,g数组不断向前偏移,所以我们要为每段数组预留出两个链长的空间,很难描述,还是要去研究代码来理解这种分配规则。可以说这是一道不看题解不好做的题目。

     1 #include<bits/stdc++.h>
     2 #define ll long long
     3 using namespace std;
     4 const int N=500005;
     5 struct node{int y,nxt;}e[N*2];
     6 int h[N],d[N],son[N],c,n,m,k,p;
     7 ll tmp[N*4],*id=tmp,*f[N],*g[N],ans=0;
     8 void add(int x,int y){
     9     e[++c]=(node){y,h[x]};h[x]=c;
    10     e[++c]=(node){x,h[y]};h[y]=c;
    11 } void dfs(int x,int fa){
    12     d[x]=1;for(int i=h[x],y;i;i=e[i].nxt)
    13     if((y=e[i].y)!=fa){
    14         dfs(y,x);d[x]=max(d[x],d[y]+1);
    15         if(d[y]>d[son[x]]) son[x]=y;
    16     } return ;
    17 } void solve(int x,int fa){
    18     if(son[x]) f[son[x]]=f[x]+1,
    19     g[son[x]]=g[x]-1,solve(son[x],x);
    20     f[x][0]=1;
    21     for(int i=h[x],y;i;i=e[i].nxt)
    22     if((y=e[i].y)!=fa&&y!=son[x]){
    23         f[y]=id;id+=d[y]*2;g[y]=id;
    24         id+=d[y]*2;solve(y,x);
    25         for(int j=0;j<d[y];j++){
    26             if(j) ans+=(f[x][j-1]*g[y][j]);
    27             ans+=(f[y][j]*g[x][j+1]);
    28         } for(int j=0;j<d[y];j++){
    29             if(j) g[x][j-1]+=g[y][j];
    30             g[x][j+1]+=f[x][j+1]*f[y][j];
    31             f[x][j+1]+=f[y][j];
    32         } 
    33     } ans+=g[x][0];return ;
    34 } int main(){
    35     scanf("%d",&n);
    36     for(int i=1,x,y;i<n;i++)
    37     scanf("%d%d",&x,&y),add(x,y);
    38     dfs(1,0);f[1]=id;id+=d[1]*2;g[1]=id;id+=d[1]*2;
    39     solve(1,0);printf("%lld
    ",ans);return 0;
    40 }
    长链剖分

    接下来是WC2010重建计划

      其实这道题可以说是点分治界的一道神题,可是用长剖也可以做,但是并不是特别主流的做法。这个如果我们dp出局部的答案,还是需要对一个区间的状态取最优的,所以我们想到了用线段树来记状态,区间取max直接维护就好,然后需要继承一些东西的时候,我们不能用指针轻易的完成这个操作了,所以我们只好借助dfs序搞出偏移量即可。

      为什么把这道题放在这个位置,首先因为它综合了其他算法,此外还是因为他的细节很多,容易手残写错,可以献给大家练习代码能力。(我的代码不知道出了什么鬼,就是不能开O2,一开O2就全T要么就全RE,不过比点分治短就是了)

    代码:

     1 #include<bits/stdc++.h>
     2 #define db double
     3 using namespace std;
     4 const int N=2000005;
     5 struct node{int y,z,nxt;}e[N];
     6 int L,U,n,son[N];double p,f[N],g[N],ans;
     7 int h[N],ww[N],d[N],pos[N],tot,c,rt,cnt,lm;
     8 struct segt{int l,r,ls,rs;db s;}t[N*4];
     9 void add(int x,int y,int z){
    10     e[++c]=(node){y,z,h[x]};h[x]=c;
    11     e[++c]=(node){x,z,h[y]};h[y]=c;
    12 } void pushup(int x){
    13     int ls=t[x].ls,rs=t[x].rs;
    14     t[x].s=max(t[ls].s,t[rs].s);
    15 } void build(int x,int l,int r){
    16     if(l==r){t[x]=(segt){l,r,-1,-1,-1e10};return ;}
    17     int mid=l+r>>1;t[x].l=l;t[x].r=r;
    18     t[x].ls=++cnt;t[x].rs=++cnt;
    19     build(t[x].ls,l,mid);build(t[x].rs,mid+1,r);
    20 } void clear(int x){
    21     t[x].s=1e-10;
    22     if(~t[x].ls) clear(t[x].ls);
    23     if(~t[x].rs) clear(t[x].rs);
    24 } db update(int x,int k,db c){
    25     if(t[x].r==t[x].l) return t[x].s=max(t[x].s,c);
    26     int mid=t[x].l+t[x].r>>1;
    27     if(k<=mid) update(t[x].ls,k,c);
    28     else update(t[x].rs,k,c);pushup(x);
    29 } db query(int x,int l,int r){
    30     if(l<=t[x].l&&t[x].r<=r)
    31     return t[x].s;db re=-1e18;
    32     int mid=t[x].l+t[x].r>>1;
    33     if(l<=mid) re=max(re,query(t[x].ls,l,r));
    34     if(mid<r) re=max(re,query(t[x].rs,l,r));
    35     return re;
    36 } void dfs(int x,int fa,int v){
    37     d[x]=1;for(int i=h[x],y;i;i=e[i].nxt)
    38     if((y=e[i].y)!=fa){
    39         dfs(y,x,e[i].z);
    40         d[x]=max(d[x],d[y]+1);
    41         if(d[y]>d[son[x]])
    42         son[x]=y,ww[x]=e[i].z;
    43     } return ;
    44 } void solve(int x,int fa){
    45     if(!pos[x]) pos[x]=++tot;
    46     int u=pos[x];g[u]=f[u]=0;//u是x在dfs序中的位置 
    47     if(son[x]) solve(son[x],x),//v是y在dfs序中的位置 
    48     g[u]+=g[u+1]+ww[x]-p,f[u]=-g[u];
    49     update(rt,u,f[u]);
    50     for(int i=h[x],y;i;i=e[i].nxt)
    51     if((y=e[i].y)!=fa&&y!=son[x]){
    52         solve(y,x);int v=pos[y],z=e[i].z;
    53         for(int j=1;j<=d[y];j++)
    54         if(L-j<d[x]){
    55             db q=query(rt,u+max(1,L-j),
    56             u+min(U-j,d[x]-1));
    57             ans=max(ans,z-p+f[v+j-1]+g[v]+g[u]+q);
    58         } for(int j=1;j<=d[y];j++)
    59         if(z-p+f[v+j-1]+g[v]>g[u]+f[u+j])
    60         f[u+j]=z-p+f[v+j-1]+g[v]-g[u],
    61         update(rt,u+j,f[u+j]);
    62     } if(d[x]-1>=L) ans=max(ans,g[u]+
    63     query(rt,u+L,u+min(U,d[x]-1)));
    64 } bool pd(db x){
    65     clear(rt);p=x;
    66     ans=-1e18;solve(1,0);
    67     return ans>=0;
    68 } int main(){ rt=++cnt;
    69     scanf("%d%d%d",&n,&L,&U);build(rt,1,n);
    70     for(int i=1,x,y,z;i<n;i++)
    71     scanf("%d%d%d",&x,&y,&z),
    72     add(x,y,z),lm=max(lm,z);
    73     dfs(1,0,0);db l=0,r=lm;
    74     while(r-l>1e-4){
    75         db mid=(l+r)/2.0;
    76         if(pd(mid)) l=mid;
    77         else r=mid;
    78     } printf("%.3lf
    ",l);return 0;
    79 }
    长链剖分

    这种算法我们就讨论到这里,其实还有不少其他的题目,希望大家有余力可以多加练习。

  • 相关阅读:
    Oracle 分析函数(Analytic Functions) 说明
    Build Your Own Oracle RAC 10g Release 2 Cluster on Linux and FireWire
    Build Your Own Oracle RAC 10g Release 2 Cluster on Linux and FireWire
    ORACLE SEQUENCE 介绍
    RAC Ocfs2文件系统常见问题解决方法
    linux 下修改日期和时间
    寒假刷题之7——波纹
    iOS 游戏 Oh my fish! 切割痕迹实现
    ACM常识
    寒假刷题之6——迷宫
  • 原文地址:https://www.cnblogs.com/Alan-Luo/p/10585344.html
Copyright © 2011-2022 走看看