[ACM]【树形DP/LCA】牛客练习赛62 牛牛染颜色

牛牛染颜色

传送门
题意:给出一个有根树,求满足条件的染色方案的数目,条件:任意两个染黑的点的LCA必须也为黑点。
[ACM]【树形DP/LCA】牛客练习赛62 牛牛染颜色_第1张图片

思路:

我一看到LCA就会想到自己还不会倍增的恐惧 很显然的树形DP。既然是DP,就只用考虑初始状态和状态转移。
状态转移方程:(dp[u][1]为将u点染黑,dp[u][0]为不染)
d p [ u ] [ 0 ] = 1 + ∑ v ∈ s o n [ u ] ( d p [ v ] [ 1 ] + d p [ v ] [ 0 ] − 1 ) dp[u][0]=1+\sum_{v\in son[u]}^{}(dp[v][1]+dp[v][0]-1) dp[u][0]=1+vson[u](dp[v][1]+dp[v][0]1)
d p [ u ] [ 1 ] = ∏ v ∈ s o n [ u ] ( d p [ v ] [ 1 ] + d p [ v ] [ 0 ] ) dp[u][1]=\prod_{v\in son[u]}^{}(dp[v][1]+dp[v][0]) dp[u][1]=vson[u](dp[v][1]+dp[v][0])
初始状态:
d p [ u ] [ 0 ] = d p [ u ] [ 1 ] = 1 dp[u][0]=dp[u][1]=1 dp[u][0]=dp[u][1]=1

对于状态转移方程的解释:

当点u不涂黑时,如果u有两个或以上子树有涂黑的点,那么就不满足“任意两个黑节点的LCA也是黑色的”。因此,只能有至多一个子树有涂黑的点。当涂黑的点存在于以v为根的子树中,其他子树必然都没涂黑,因此情况数等于以v为根的子树的总情况数: d p [ v ] [ 1 ] + d p [ v ] [ 0 ] dp[v][1]+dp[v][0] dp[v][1]+dp[v][0]。每个子树都有可能成为那个特殊的子树,因此情况累加。但是,我们要注意, d p [ v ] [ 0 ] dp[v][0] dp[v][0]包含以v为根的子树都不涂黑的情况(而此时其他树也都没有黑点:即空集),每加上这样一项,都加上了一个空集,重复了。于是每加这样一项都把空集减去(-1)。空集在最外面加回来就行了。

当点u涂黑时,点u的所有儿子都可以选择涂黑或不涂,这样都满足LCA也为黑色。那么情况数就等于每个子树的合法情况数目之间相乘的(因为相互不影响)。每棵子树的情况数目等于子树的根v涂与不涂的情况数目之和。

初始状态:涂黑算一种情况,不涂也是一种情况。

代码:

#include
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int maxn=1000005;
ll head[maxn],tot=0;
ll dp[maxn][3];
struct node{
	int to,next;
}e[maxn*2];
void adde(int u,int v){
	++tot;
	e[tot].next=head[u];
	e[tot].to=v;
	head[u]=tot;
}
inline int read(){
	int f=1,x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
//运用dfs
void dfs(int u,int fa){
	dp[u][0]=dp[u][1]=1;
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		dp[u][1]=(dp[u][1]*(dp[v][0]+dp[v][1]))%mod;
		dp[u][0]=(dp[u][0]+dp[v][0]+dp[v][1]-1)%mod;
	}
}
int main(){
	int n=read();
	for(int i=1;i<=n-1;i++){
		int u=read(),v=read();
		adde(u,v);
		adde(v,u);
	}
	dfs(1,0);
	printf("%lld\n",(dp[1][0]+dp[1][1])%mod);
}

你可能感兴趣的:(图论,动态规划)