ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

树形(换根) dp

2021-04-12 22:02:12  阅读:233  来源: 互联网

标签:head sizes int dfs fa 树形 换根 dp


树形dp(换根dp)

一句话总结重点:第一次dfs搜索所有点,得出所有点状态的值,第二次dfs对于各种状态进行计算,从而得出所需要的答案。

dfs设计:

总结:

第一次扫描时,任选一个点为根,在“有根树”上执行一次树形dp,在回溯时,自底向上的状态转移。

第二次扫描时,从第一次选的根出发,对整根树执行一个dfs,在每次递归前进行自顶向下的转移,计算出换根后的解。

  1. 一半来说,都需要存储树的大小,所以我们定义 \(size\) 作为这个节点子树的大小。因此有一下代码:
sizes[x]=1;
...
dfs(y,x);
sizes[x]+=sizes[y];
  1. 其他状态需要自己设置,一般来说,树形dp的题目都是跟树的节点数目有关的。

  2. 进入到第二遍dfs中,我们就需要运用 容斥原理 去求出答案:

    例如P3047 [USACO12FEB]Nearby Cows G 中,我们去计算距离不超过k的点的点权之和。因为在第一遍dfs中,我们运用了:

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

如果是这样的话,我们对于距离为 \(x\) 的遍,都多算了 \((k-x)\) 次,因此在第二次dfs中有以下代码:

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 没有上司的舞会

解题思路:

\(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;
}

标签:head,sizes,int,dfs,fa,树形,换根,dp
来源: https://www.cnblogs.com/guanlexiangfan/p/14650528.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有