题意:给你一棵树,树上有一些重要的点,让你选一个只包含重要的点的点集,满足点集里最远的两个点不超过k,问有多少种选法
想了好久,题解说得太简略了。。。
dp[i][j]表示在i这颗子树中距离i点最远点距离为j的方案数。
考虑如何从子树转移,显然 我们遍历到一颗新子树时,要用之前的所有方案乘以这颗子树的方案来更新。
即dp[u][max(i,j+1) ]+=dp[u][i]*dp[to][j]
暴力更新的复杂度是k^2的。需要用前缀和维护一下,
最后如果这个点可选,那么有选这个点和不选这个点两种情况,要乘2,然后加上只有这个点的情况。
最后计算总和的时候,如果这个点不是根,只加上长度为k的情况,因为其他情况还有可能去更新,会在他的祖宗里计算到,如果不是加上所有方案。
#include <bits/stdc++.h>
using namespace std;
#define N 5010
#define ll long long
#define mod 1000000007
#define go(i,a,b) for(int i=(a);i<=(b);i++)
#define dep(i,a,b) for(int i=(a);i>=(b);i--)
#define add(a,b) ((a+=(b))%=mod)
ll dp[N][N],sum[N][N],f[N],ans;
vector<int>path[N];
int is[N];
int n,m,k,x,y;
void cal(int u,int to){
go(i,1,k-1)f[i]=dp[u][i]*sum[to][min(i,k-i)-1]%mod;
go(i,1,k-1)add(f[i],sum[u][min(i,k-i)]*dp[to][i-1]%mod);
go(i,1,k/2)add(f[i],(mod-dp[u][i]*dp[to][i-1]%mod)%mod);//容斥,减去计算重复的方案
go(i,1,k)
add(dp[u][i],(dp[to][i-1]+f[i])%mod),//加上这颗子树的方案
sum[u][i]=(sum[u][i-1]+dp[u][i])%mod;
}
void dfs(int u,int fa){
for(auto to:path[u]){
if(to==fa)continue;
dfs(to,u);
cal(u,to);
}
if(is[u]){//如果这个点可选
sum[u][0]=dp[u][0]=1;//只选这个点
go(i,1,k)
add(dp[u][i],dp[u][i]),//分选它不选两种,所以要乘2
sum[u][i]=(sum[u][i-1]+dp[u][i])%mod;//更新前缀和
}
add(ans,u!=1?dp[u][k]:sum[u][k]);//如果这个点是根,加所有情况,否则只加长度为k的情况
}
int main()
{
cin>>n>>m>>k;
go(i,2,n)
scanf("%d%d",&x,&y),
path[x].push_back(y),
path[y].push_back(x);
go(i,1,m)scanf("%d",&x),is[x]=1;
dfs(1,0);
cout<<ans<<endl;
}