樹形(換根) dp

樹形dp(換根dp)

一句話總結重點:第一次dfs搜索全部點,得出全部點狀態的值,第二次dfs對於各類狀態進行計算,從而得出所須要的答案。c++

dfs設計:

總結:spa

第一次掃描時,任選一個點爲根,在「有根樹」上執行一次樹形dp,在回溯時,自底向上的狀態轉移。設計

第二次掃描時,從第一次選的根出發,對整根樹執行一個dfs,在每次遞歸前進行自頂向下的轉移,計算出換根後的解。code

  1. 一半來講,都須要存儲樹的大小,因此咱們定義 \(size\) 做爲這個節點子樹的大小。所以有一下代碼:
sizes[x]=1;
...
dfs(y,x);
sizes[x]+=sizes[y];
  1. 其餘狀態須要本身設置,通常來講,樹形dp的題目都是跟樹的節點數目有關的。遞歸

  2. 進入到第二遍dfs中,咱們就須要運用 容斥原理 去求出答案:ci

    例如P3047 [USACO12FEB]Nearby Cows G 中,咱們去計算距離不超過k的點的點權之和。由於在第一遍dfs中,咱們運用了:get

for(int j=1;j<=k;j++)
    f[x][j]+=f[y][j-1];

若是是這樣的話,咱們對於距離爲 \(x\) 的遍,都多算了 \((k-x)\) 次,所以在第二次dfs中有如下代碼:it

for(int j=k;j>=2;j--)
    f[y][j]-=f[y][j-2];
for(int j=1;j<=k;j++)
    f[y][j]+=f[x][j-1];

樹形dp公式:

void dfs(int x,int fa){
    sizes[x]=1;
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==fa) continue;
        dfs(y,x);
        sizes[x]+=size[y];
        ....
        ....
    }
}
void dfs2(int x,int fa)[
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==fa) continue;
        ...
        ...
        ...
        ans[x]=...
        dfs(y,x);
    }
]
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>x>>y;
        add(x,y);add(y,x);
    }
    dfs(1,0);dfs2(1,0);
    cout<<ans<<endl;
    return 0;
}

例題:

模板P1352 沒有上司的舞會模板

解題思路:class

\(dp[i][1]\) 表示選擇該節點所得到的最大值,\(dp[i][0]\) 表示不選擇該節點得到的最大值。

所以咱們就能夠輕鬆寫出來代碼:

#include<bits/stdc++.h>
using namespace std;
vector<int> son[10010];
int f[10010][2],v[10010],h[10010],n;
void dp(int x){
	f[x][0]=0;//0表示其自己不參加 
	f[x][1]=h[x]; //自己參加 
	for(int i=0;i<son[x].size();i++){
		int y=son[x][i];//兒子的地址 
		dp(y);
		f[x][0]+=max(f[y][0],f[y][1]);//兒子節點參加或不參加的最大值 
		f[x][1]+=f[y][0];//每一個兒子節點不參加的最大值 
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>h[i];
	for(int i=1;i<n;i++){
		int x,y;
		cin>>x>>y;
		v[x]=1;//有爹 
		son[y].push_back(x);
	}
	int root;
	for(int i=1;i<=n;i++)
		if(!v[i]){//沒爹,即根節點 
			root=i;
			break;
		}
	dp(root);
	cout<<max(f[root][1],f[root][0])<<endl;
	return 0;
}

CF708C Centroids

咱們知道,一個節點不能做爲重心,有且僅有一個子樹大小大於 \(\lfloor\dfrac{n}{2}\rfloor\),咱們必定是從這個子樹裏面選一個子樹接在當前的根上。

那麼就找一個這樣的子樹就能夠啦

考慮 \(dp[u]\) 的最佳轉移點,若是 \(dp[u]\) 最佳轉移點就是\(v\) ,那麼咱們就須要獲得一個點第二大的可以去除的子樹大小

因此必須考慮維護兩個 \(dp\) 值,\(dp[u][0]\) 表示第一大的值,\(dp[u][1]\) 表示第二大的值。

這樣咱們就能夠解出這道題了。

#include<bits/stdc++.h>
using namespace std;
const int N=8e5+5;
int n;
int nxt[N],ver[N],head[N],tot;
int dp[N][2],pos[N];
int sizes[N],maxsizes[N];
int ans[N],[N];
void add(int x,int y){
    ver[++tot]=y;
    nxt[tot]=head[x];
    head[x]=tot;
}
void dfs(int x,int fa){
    sizes[x]=1;
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==fa) continue;
        dfs(y,x);     int v;
        sizes[x]+=sizes[y];
        if(sizes[y]>sizes[maxsizes[x]]) maxsizes[x]=y;// 最多子節點的樹
        if(sizes[y]<=n/2) v=sizes[y];//子節點不足
        else v=dp[y][0];
        if(dp[x][0]<v){
            dp[x][1]=dp[x][0];
            dp[x][0]=v;pos[x]=y;
        } 
        else if(dp[x][1]<v) dp[x][1]=v;
    }
}
void dfs2(int x,int fa){
    ans[x]=1;
    if(sizes[maxsizes[x]]>n/2) ans[x]=(sizes[maxsizes[x]]-dp[maxsizes[x]][0]<=n/2);
    else if(n-sizes[x]>n/2) ans[x]=(n-sizes[x]-f[x]<=n/2);
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==fa) continue; int v;
        if(n-sizes[x]>n/2) v=f[x];
        else v=n-sizes[x];
        f[y]=max(f[y],v);
        if(pos[x]==y) f[y]=max(f[y],dp[x][1]);
        else          f[y]=max(f[y],dp[x][0]);
        dfs2(y,x);
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<n;i++){
        int x,y;scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    dfs(1,0);dfs2(1,0);
    for(int i=1;i<=n;i++)
        printf("%d ",ans[i]);
    //system("pause");
    return 0;
}
相關文章
相關標籤/搜索