1. 程式人生 > 實用技巧 >三色樹——需要深度思考的樹形dp

三色樹——需要深度思考的樹形dp

三色樹

給出一個N個節點的無根樹,每條邊有非負邊權,每個節點有三種顏色:黑,白,灰。
一個合法的無根樹滿足:樹中不含有黑色結點或者含有至多一個白色節點。
現在希望你通過割掉幾條樹邊,使得形成的若干樹合法,並最小化割去樹邊權值的和。

第一行一個正整數N,表示樹的節點個數。
第二行N個整數Ai,表示i號節點的顏色,0表示黑色,1表示白色,2表示灰色。
接下來N-1行每行三個整數XiYiZi,表示一條連線Xi和Yi權為Zi的邊。

輸出一個整數表示其最小代價。

5
01110
125
133
525
2416

10
樣例解釋:
花費10的代價刪去邊(1,2)和邊(2,5)。

20%的資料滿足N≤10。
另外30%的資料滿足N≤100,000,且保證樹是一條鏈。

100%的資料滿足N≤300,000,0≤Zi≤1,000,000,000,Ai∈{0,1,2}。

分析:

其實明眼人都能看出這是樹形dp,可是當我們仔細去思考該維護什麼時,我們就陷進去了。因為我們所想的任何方法的維護都十分的複雜,很容易給人一種思路錯了的錯覺。可是這題確確實實就是這麼複雜,很多人想得到方向,卻無法深入,接下來分析一下。

我們要維護的是3類情況(用f來表示):

1.f[i][0]表示以i為根節點的子樹在切割後不含黑點的最小代價;

2.f[i][1]表示以i為根節點的子樹在切割後不含白點的最小代價;

3.f[i][2]表示以i為根節點的子樹在切割後含一個白點的最小代價;

按這樣分之後答案就是根節點s三個值的最小值

接下來考慮轉移方程:

1.當col[i](即該點顏色)=0時,明顯不符合無黑點,所以f[i][0]=inf(無窮大);而當col[i]!=0時,這時要考慮斷邊情況,很容易可以列得

$f[i][0]= \sum_{son}$min(f[son][0],min(f[son][1],f[son][2])+w)<--w為邊權

2.當col[i]=1時,同樣的不符合無白點,所以f[i][0]=inf;而當col[i]!=0時,這時要考慮斷邊情況,一樣可以列得

$f[i][1]= \sum_{son}$min(f[son][1],min(f[son][0],f[son][2])+w) (2與1幾乎一模一樣)

3.當col[i]=1時,這時該點已經是一個白點了,所以方程式和2情況的第二種一樣。剩下最後一種情況(最複雜的一種),即col[i]!=1時,這時我們直接處理的話會列出一長串,但我們可以和f[i][1]結合起來,我們只要從最後算出的f[i][1]中減去一種min(f[son][1],min(f[son][0],f[son][2])+w)再加上f[son][2]就能維護一個白點的情況,同樣的,我們要使f[i][2]最小化,而最終的

f[i][1]又是個定值,所以我們要最大化min(f[son][1],min(f[son][0],f[son][2])+w)+f[son][2].

所以最終方程式為:$f[i][2]= f[i][1](最終的)-max(min(f[son][1],min(f[son][0],f[son][2])+w)-f[son][2]).<--減號是因為加了個括號

程式碼:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<queue>
 6 using namespace std;
 7 #define debug printf("zjyvegetable\n")
 8 #define int long long
 9 inline int read(){
10     int a=0,b=1;char c=getchar();
11     while(!isdigit(c)){if(c=='-')b=-1;c=getchar();}
12     while(isdigit(c)){a=a*10+c-'0';c=getchar();}
13     return a*b;
14 }
15 const int N=4e5+50,M=2e6+50,inf=123456789012345678;
16 int n,col[N],tot,vis[N],h[N],ver[M],nx[M],ed[M],f[N][3],t[M],top;
17 void add(int u,int v,int z){
18     ver[++tot]=v;ed[tot]=z;
19     nx[tot]=h[u];h[u]=tot;
20 }
21 inline void dfs(int x){
22     vis[x]=1;
23     if(col[x]==0)f[x][0]=inf;
24     else if(col[x]==1)f[x][1]=inf;
25     int maxn=0;
26     for(int i=h[x];i;i=nx[i]){
27         int v=ver[i];
28         if(vis[v])continue;
29         dfs(v);
30         if(col[x]==0){
31             f[x][1]+=min(f[v][1],min(f[v][0],f[v][2])+ed[i]);
32             maxn=max(maxn,min(f[v][1],ed[i]+min(f[v][0],f[v][2]))-f[v][2]);
33         }
34         else if(col[x]==1){
35             f[x][0]+=min(f[v][0],min(f[v][1],f[v][2])+ed[i]);
36             f[x][2]+=min(f[v][1],ed[i]+min(f[v][0],f[v][2]));
37         }
38         else{
39             f[x][0]+=min(f[v][0],min(f[v][1],f[v][2])+ed[i]);
40             f[x][1]+=min(f[v][1],min(f[v][0],f[v][2])+ed[i]);
41             maxn=max(maxn,min(f[v][1],ed[i]+min(f[v][0],f[v][2]))-f[v][2]);
42         }
43     }
44     if(col[x]!=1)f[x][2]=f[x][1]-maxn;
45 }
46 signed main(){
47     //freopen("tree2.in","r",stdin);
48     //freopen("tree2.out","w",stdout);
49     int u,v,z;
50     n=read();
51     for(int i=1;i<=n;i++){
52         col[i]=read();
53     }
54     for(int i=1;i<n;i++){
55         u=read();v=read();
56         z=read();
57         add(u,v,z);add(v,u,z);
58     }
59     dfs(1);
60     printf("%lld\n",min(f[1][0],min(f[1][1],f[1][2])));
61     return 0;
62 }

留在最後的話:

由於這題資料有長鏈,所以要用人工棧,而筆者由於懶而只打了dfs,拿不到全部分,望理解。