1. 程式人生 > >長鏈剖分優化dp三例題

長鏈剖分優化dp三例題

科技 class hot else bit blank 答案 scan hid

  首先,重鏈剖分我們有所認識,在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\n",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\n",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\n",l);return 0;
79 }
長鏈剖分

這種算法我們就討論到這裏,其實還有不少其他的題目,希望大家有余力可以多加練習。

長鏈剖分優化dp三例題