[AcWing 758. 切割树] 树形DP

题目链接
题意:
是给一棵树,每个节点有颜色,只有白色和黑色,切成若干部分,使得每部分只有一个白色点。求切割方案。输出对1000000007取模后的结果。

输入格式

第一行仅包含一个正整数n,表示树的结点数量。1≤n≤105

第二行包含n-1个数字,第 i 个数字表示第 i 个结点的根,我们认为 0 号结点是整棵树的根.第 i 个数字不超过 i,即第 i 个结点的根一定是编号小于 i 的结点。

第三行包含n个数字,第 i 个数字表示第 i-1 个结点的颜色,0表示白色,1表示黑色。

输出格式

输出一个整数表示对1000000007取模后的结果。

输入样例1:

3
0 0
1 0 0

输出样例1:

2

输入样例2:

10
0 0 1 2 0 5 1 2 3
1 0 0 1 0 0 1 1 0 1

输出样例2:

3

思路: 树形DP

状态设计

 f[i][0]代表i节点切割出去的儿子都符合条件,且 i 点所在的连通块有白色点。
 f[i][1]代表i节点切割出去的儿子都符合条件,且 i 点所在的连通块没有白色点

考虑第i个节点的情况
1. 当 i 点是白色点的时候。f[i][1] = 0因为他不可能存在一个连通块内并且不包含白色。f[i][0]由其所有的儿子的方案累乘。具体来说,对于一个儿子j,可以选择切割这条边,代表这个儿子所在的连通块包含白色,+f[j][0],也可以不切这条边,代表儿子所在的连通块没有白色, +f[j][1]
[AcWing 758. 切割树] 树形DP_第1张图片

2. 当 i 点是黑色点的时候。f[i][1]也是同理由其儿子选择切割与不切割转移来,f[i][0]则是由贡献白点的儿子转移,由于只能有一个儿子贡献白点,其他儿子贡献的是全黑点的连通块,或者切掉,所以各个儿子贡献的方案之间是加法关系
[AcWing 758. 切割树] 树形DP_第2张图片

这里的由于需要 i != j 的乘法积,最好分别用前缀积、后缀积数组保存起来


代码中有详细解释,注意每次计算结果是否超出范围,常取模

#include
#include
using namespace std;
typedef long long LL;
const int N=1e6+5,mod=1e9+7;
int n;
int len,e[N],h[N],ne[N];
bool color[N];
int f[N][2];//f[i][0]表示包含i这个节点的树里面没有白色的节点,f[i][1]表示有一个白色节点
int q[N],l[N],r[N];//l前缀数组,r后缀数组,q用来保存取出来的儿子
//邻接表存储树,添加u->v这条边
void add(int u,int v) {
     
	e[len]=v;
	ne[len]=h[u];
	h[u]=len++;
}
void dfs(int u) {
     
	//先递归的计算所有的子节点
	for(int i=h[u]; ~i; i=ne[i])
		dfs(e[i]);
	if(color[u]==0) {
     //如果当前是白色,显然f[u][0]=0,f[u][1]=1;
		f[u][0]=0,f[u][1]=1;
		//下面去计算出f[u][1];
		for(int i=h[u]; ~i; i=ne[i]) {
     //遍历u的所有的儿子
			int j=e[i];//取出儿子的节点
			f[u][1]=(LL)f[u][1]*(f[j][1]+f[j][0])%mod;//u这个节点本身计算白色,现在要去求出所有儿子没有贡献白色的节点
			//1. 就是j这个节点没有白色 即f[j][0];
			//2. j这个有白色,但是i和j断开,断开后j这个节点必须要有一个白色节点的方案数,即f[j][1];
			//显然两种情况都只能存在一种,所以此时方案数x=f[j][1]+f[j][0];
			//而每个儿子互不影响,情况相同,所以f[u][1]=∏(xj) ,0<=j<=k,k为u的儿子数
		}
	} else {
     
		//当前是黑色的节点,考虑f[u][0],同样f[u][0]的初值为1;
		f[u][0]= 1,f[u][1]=0;
		//因为这里不管是计算f[u][0]还是计算f[u][1]都需要用到u的儿子;
		//所有取出所有的儿子放入q数组中,用k记录儿子数量
		int k=0;
		for(int i=h[u]; ~i; i=ne[i]) {
     
			q[++k]=e[i];
		}
		//计算f[u][0],其实f[u][0]的计算与"当u为白色的时候,去算f[u][1]" 的思路类似
		// 即先找出u的儿子f[i][0]的方案数,然后找到f[i][1],但是断开他们
		for(int i=1; i<=k; i++)
			f[u][0]=(LL)f[u][0]*(f[q[i]][1]+f[q[i]][0])%mod;
		//下面是最难的,计算f[u][1],因为当前的u为黑色,它能拥有白色只能说明白色来自于儿子所在的树贡献出来,且只能有一个
		//不妨设第个j儿子贡献白色,则其他的儿子都要与u断开,且其他的儿子必须包含白色
		//此时方案数 x = f[j][1] * [∏(f[i][1]+f[i][0]),1<=i<=k,i!=j,k为所有u的儿子]
		//因此所有的方案数f[u][1]=∑x (1<=x<=k)

		//所以为了方便就要预处理,先计算除去j的前j-1的乘积和后j+1的乘积,即j的前缀数组、后缀数组;
		//下面计算前缀、后缀数组
		l[0]=r[k+1]=1;
		for(int i=1; i<=k; i++)
			l[i]=(LL)l[i-1]*(f[q[i]][1]+f[q[i]][0])%mod;
		for(int i=k; i>=1; i--)
			r[i]=(LL)r[i+1]*(f[q[i]][1]+f[q[i]][0])%mod;

		//计算f[u][1]
		for(int i=1; i<=k; i++)
			f[u][1]=(f[u][1]+(LL)l[i-1]*r[i+1]%mod*f[q[i]][1])%mod;
	}
}
int main() {
     
	cin>>n;
	memset(h,-1,sizeof(h));
	int x;
	for(int i=1; i<n; i++) {
     
		cin>>x;
		add(x,i);
	}
	for(int i=0; i<n; i++) {
     
		cin>>color[i];
	}
	dfs(0);
	cout<<f[0][1]<<endl;
	return 0;
}

你可能感兴趣的:([AcWing 758. 切割树] 树形DP)