题意:
在一棵树上进行涂色,总共有
m 种颜色(编号1−m ),其中编号为k 的颜色为特殊颜色,如果树上一个节点染上了特殊颜色,则其邻居节点的颜色编号都应该<k ,求在这棵树上染色总共有多少种方法,染上特殊颜色的节点的数目不超过x 个。
n≤105, m≤109, x≤10
思路:
难点:状态定义 + 状态转移方程细节!
比较明显的树形dp。
首先是状态定义,如果我们仅定义:
dp[u][i][0] :u 为根节点的子树,使用了i 个特殊颜色,且该当前根节点没有使用特殊颜色;
dp[u][i][1] :u 为根节点的子树,使用了i 个特殊颜色,且该当前根节点使用了特殊颜色;
你会发现转移的时候,dp[u][i][0] 是没有办法求出来的。。。
所以我们定义:
dp[u][i][0] :u 为根节点的子树,使用了i 个特殊颜色,且该当前根节点颜色编号小于k ;
dp[u][i][1] :u 为根节点的子树,使用了i 个特殊颜色,且该当前根节点颜色编号等于k ;
dp[u][i][2] :u 为根节点的子树,使用了i 个特殊颜色,且该当前根节点颜色编号大于k ;
所以转移的过程我们只需要根据限制条件来就可以了。。。
以求dp[u][i][0] 为例,枚举子节点的特殊颜色贡献值j(j≤i) ,此时子节点颜色编号可以取任意值,即总的方案数为:dp[u][i−j][0]∗(dp[v][j][0]+dp[v][j][1]+dp[v][j][2]) ,将这个值累加即可。要注意的是,在
u 节点枚举i 的时候,是从大往小枚举,因为这样可以确保我使用的dp[u][i−j][0] 是来自前一个u 的子节点计算后得到的值,如果从小往大枚举,那么这个值就会被覆盖掉。
代码:
#include <bits/stdc++.h>
const int N = 100005, mo = 1000000007, X = 12;
int h[N],f1[N][X],f2[N][X],f3[N][X],n,m,k,z,u,v,xb,ans,i;
struct edge
{
int to, next;
}e[N<<1];
inline void addedge(int u,int v)
{
e[++xb] = (edge){v,h[u]}, h[u] = xb;
e[++xb] = (edge){u,h[v]}, h[v] = xb;
}
void dfs(int x,int fa)
{
f1[x][1] = 1,f2[x][0] = z-1,f3[x][0] = m-z;
for(int i = h[x];i;i = e[i].next)
if(e[i].to!=fa)
{
dfs(e[i].to,x);
for(int j = k;j >= 0;-- j)
{
int s1 = 0, s2 = 0,s3 = 0;
for(int l = 0;l <= j;++ l)
{
if(l<j) s1 = (s1 + 1ll*f1[x][j-l] * f2[e[i].to][l])%mo;
s2 = (s2 + 1ll*f2[x][j-l] * (1ll*f1[e[i].to][l] + f2[e[i].to][l] + f3[e[i].to][l]))%mo;
s3 = (s3 + 1ll*f3[x][j-l] * (1ll*f2[e[i].to][l] + f3[e[i].to][l]))%mo;
}
f1[x][j] = s1,f2[x][j] = s2,f3[x][j] = s3;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(i = 1;i < n;++ i)
{
scanf("%d %d", &u, &v);
addedge(u, v);
}
scanf("%d %d", &z, &k);
dfs(1, 0);
for(i = 0;i <= k;++ i) ans = (1ll*ans + f1[1][i] + f2[1][i] + f3[1][i]) % mo;
return printf("%d\n", ans), 0;
}