给定一个n个点的树,把其中一些点涂成黑色,使得对于每个点,它离与它最近的黑点的距离不超过K,求方案总数.
树形dp
我们设\(dp[i][r]\)为
状态:当前点为i,离i最近的黑点与i距离为r.
dp[i][r]的意义: 在i的子树内 达成该状态的方案数
更新方式当然是递归,让儿子更新父亲,同时把儿子的答案统计到父亲上,这样就把答案汇总到根上了.
一开始我想的是r取值为0~k,结果是不行的,因为离i最近的黑点不仅可能在i的子树内,还可能在i的子树外
但我们(在更新dp[i][r]时)只考虑了黑点在i的子树内的情况.
所以r的取值应该在0~2*k间,这样 即使不合法也要记下来(重要!!) ,因为它子树外的黑点仍然可以使它变成合法的.
然后我们考虑如何转移
设v为u的儿子枚举v和u的dp值第二维
对于dp[u][i]和dp[v][r],我们这样转移
如果\(i+(r+1)<=2\times k+1\)那么\(tmp[min(i,r+1)]+=dp[u][i]\)(tmp为临时数组,转移后赋值到dp[u][i]上,代表u子树内的方案数统计)
这个方程代表的意思是该方案可行时的转移,然后min(i,r+1)的意思是
因为这个方案自身的问题已经解决,还(有余力)可以用来更新其他状态,简单的说就是min(i,r+1)代表的那个黑点还可以发挥作用
如果\(i+(r+1)>2\times k+1\)那么\(tmp[max(i,r+1)]+=dp[u][i]\)
这个方程代表的意思是该方案不可行时的转移,然后max(i,r+1)的意思是
因为这个方案自身的问题还没解决,需要别的黑点来更新它,
而因为要更新(使这个方案变得可行)就要照顾到所有点,而其中离黑点最远的点在哪里呢,就是在max(i,r+1)对应的黑点到u的链上,所以我们要把这条链更新,就应该记max(i,r+1)
r+1是因为从u到v有1的距离
初始化:dp[u][0]=1(u为黑点)dp[u][k+1]=1(u不为黑点,需要一个距离小于等于k的点来更新它)
#include<bits/stdc++.h>
#define N 205
#define int long long//懒
using namespace std;
const int mod=1e9+7;
vector<int> G[N];
int dp[N][N];
int plk[N];
int n,k;
void dfs(int x,int fa){
dp[x][0]=dp[x][k+1]=1;
for(int i=0;i<G[x].size();i++){
int to=G[x][i];
if(to==fa)continue;
dfs(to,x);
for(int i=0;i<=2*k+1;i++)plk[i]=0;
for(int r=0;r<=2*k+1;r++)
for(int p=0;p<=2*k;p++){
if(r+p<=2*k)(plk[min(r,p+1)]+=1ll*dp[x][r]*dp[to][p])%=mod;
else (plk[max(r,p+1)]+=1ll*dp[x][r]*dp[to][p])%=mod;
}
for(int r=0;r<=2*k+1;r++)dp[x][r]=plk[r];
}
}
signed main(){
scanf("%lld%lld",&n,&k);
for(int i=1;i<n;i++){
int a,b;
scanf("%lld%lld",&a,&b);
G[a].push_back(b);
G[b].push_back(a);
}
dfs(1,0);
int ans=0;
for(int i=0;i<=k;i++)(ans+=dp[1][i])%=mod;
cout<<ans;
}