题解 P4084 【[USACO17DEC]Barn Painting】
又是一道树形dp的题
这道题和P1352 没有上司的舞会还是挺像的,这个可能更加复杂一些。不过我觉得难度可能都还属于树形dp的入门题。
树形dp本质上来说就是dfs一遍树然后进行递推,这道题中可以开个二维的数组dp[i][j]表示编号为i的点涂上j颜色的方案数。
显然每个点的方案数和它的子树是有关的,所以我们要先遍历到叶子节点然后一步步往回推。
由于相邻的点颜色不能相同,所以转移的时候只能由颜色不同的转移过来。且当前点的方案数应为其子树方案数的乘积。打个比方,对于点i来说,它有两个子树j,k。那么转移方程应为
dp[i][1]*=(dp[j][2]+dp[j][3])*(dp[k][2]+dp[k][3]);
dp[i][2]*=(dp[j][1]+dp[j][3])*(dp[k][1]+dp[k][3]);
dp[i][3]*=(dp[j][1]+dp[j][2])*(dp[k][1]+dp[k][2]);
至于为什么是*=,这就涉及到了dp初始值的设立。
对于一个最初没有涂色的点i,很显然刚遍历到的时候我们设置点i的初始值 dp[i][1]=dp[i][2]=dp[i][3]=1;
而如果点i已经被上色了,那么只有被上的颜色的数组才能为1(dp[i][col[i]]=1),而其他2个都为0。
所以之前用*=来更新保证了如果当前点已经被上色了的话,上其他颜色的方案数为0。
然后随便选个点i为根跑一遍dfs输出dp[i][1]+dp[i][2]+dp[i][3]就好了
#include<cstdio>
#include<iostream>
#include<cstring>
#define p 1000000007
#define LL long long
using namespace std;
inline int read()
{
int sum=0;
char ch =getchar();
while(ch<'0'||ch>'9')
ch=getchar();
while(ch>='0'&&ch<='9')
{
sum=sum*10+ch-'0';
ch=getchar();
}
return sum;
}
int n,m,tot=0;
int Head[100005],col[100005];//Head用于邻接表 col记录颜色
LL dp[100005][4];
bool visit[100005];//由于存图为无向图,所以要记录下已经走过的点防止死循环
struct tree
{
int next,node;
}h[200010];
inline void add(int u,int v)//邻接表存图
{
h[++tot].next=Head[u];
h[tot].node=v;
Head[u]=tot;
}
void dfs(int pos)//dfs遍历
{
visit[pos]=1;
if(col[pos])//如果已经上过色了,其他两种颜色的方案数为0。
dp[pos][col[pos]]=1;
else//三种颜色都可以被♂上
{
dp[pos][1]=1;
dp[pos][2]=1;
dp[pos][3]=1;
}
for(register int i=Head[pos];i;i=h[i].next)//找到当前点所有的子节点
{
int v=h[i].node;
if(!visit[v])
{
dfs(v);//一直向下遍历直到叶子节点返回
dp[pos][1]=dp[pos][1]*((dp[v][2]+dp[v][3])%p)%p;
dp[pos][2]=dp[pos][2]*((dp[v][1]+dp[v][3])%p)%p;
dp[pos][3]=dp[pos][3]*((dp[v][2]+dp[v][1])%p)%p;//转移 记得取模!
}
}
}
int main()
{
int x,y;
n=read();
m=read();
for(register int i=1;i<n;++i)
{
x=read();
y=read();
add(x,y);
add(y,x);//加边
}
for(register int i=1;i<=m;++i)
{
x=read();
y=read();
col[x]=y;//记录颜色
}
dfs(1);//随便把一个点当根就好了
cout<<(dp[1][1]+dp[1][2]+dp[1][3])%p<<endl;
return 0;
}