題目大意:樹上有 m 個關鍵點,選出一部分點,使得任意一對點間的距離不大於 ,問有多少種選法。
容易想到構造狀態 表示在 結點爲根結點的子樹中選的結點深度不大於 的方案總數。
這題的狀態構造方法類似於 https://codeforc.es/contest/1249/problem/F
不過這題是計數類的DP,計數類的DP比較難處理,需要將情況不重複不遺漏的統計顯然更難了,尤其是樹上計數的DP。
直接暴力轉移的複雜度爲 ,用前綴和優化一下,轉移複雜度可以降低到 。但會出現重複的情況,用容斥去除即可。
關於統計答案,顯然也會出現重複的情況,可以按深度來劃分統計。
詳見代碼
代碼
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int maxn = 5e3 + 10;
vector<int> g[maxn];
ll dp[maxn][maxn];
ll sum[maxn][maxn];
ll f[maxn],ans = 0;
bool is[maxn];
int n,m,k;
void dfs(int u,int fa) {
// 爲了更好轉移,前綴和用另外一個數組sum[u][dep],dep[u][dep]表示最深的點的深度爲 dep 的方案
// sum[u][dep] 則表示最深的深度不大於 dep 的方案
for(auto it : g[u]) {
if(it == fa) continue;
dfs(it,u);
for(int i = 1; i <= k - 1; i++) { //f[i] 用來統計考慮當前子樹的答案
f[i] = dp[u][i] * sum[it][min(i,k - i) - 1] % mod;
f[i] += dp[it][i - 1] * sum[u][min(i,k - i)] % mod;
f[i] %= mod;
}
for(int i = 1; i <= k / 2; i++) //容斥去除重複的
f[i] = (f[i] + mod - dp[u][i] * dp[it][i - 1] % mod) % mod;
for(int i = 1; i <= k; i++) {
//加上考慮了當前這一棵子樹的答案
dp[u][i] += dp[it][i - 1] + f[i];
dp[u][i] %= mod;
sum[u][i] = (sum[u][i - 1] + dp[u][i]) % mod;
}
}
if(is[u]) {
//若根節點點可選,dp[u][i]要乘2,因爲根節點有選和不選兩種情況
sum[u][0] = dp[u][0] = 1; //一個點都不選不能算一種方案,因此初始化1,前面的更新同理,因此不會重複
for(int i = 1; i <= k; i++) {
dp[u][i] = dp[u][i] * 2 % mod;
sum[u][i] = (sum[u][i - 1] + dp[u][i]) % mod;
}
}
if(u == 1) ans = (ans + sum[u][k]) % mod; //當u 是根結點時,統計深度爲 k 以內的所有方案
else ans = (ans + dp[u][k]) % mod; //否則,只統計深度爲 k 的答案
//相當於對深度大於 k 的情況,按深度子樹根節點和深度 k + 1,k + 2 分開統計,這樣一定不會重複
}
int main() {
scanf("%d%d%d",&n,&m,&k);
for(int i = 1; i < n; i++) {
int u,v;scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
for(int i = 1; i <= m; i++) {
int x;scanf("%d",&x);
is[x] = true;
}
dfs(1,0);
printf("%lld\n",ans);
return 0;
}