HDU 6540 Neko and tree(樹形DP,計數類DP)

在這裏插入圖片描述


題目大意:樹上有 m 個關鍵點,選出一部分點,使得任意一對點間的距離不大於 kk,問有多少種選法。

容易想到構造狀態 dp[u][dep]dp[u][dep] 表示在 uu 結點爲根結點的子樹中選的結點深度不大於 depdep 的方案總數。

這題的狀態構造方法類似於 https://codeforc.es/contest/1249/problem/F

不過這題是計數類的DP,計數類的DP比較難處理,需要將情況不重複不遺漏的統計顯然更難了,尤其是樹上計數的DP

直接暴力轉移的複雜度爲 O(n2)O(n^2),用前綴和優化一下,轉移複雜度可以降低到 O(n)O(n)。但會出現重複的情況,用容斥去除即可。

關於統計答案,顯然也會出現重複的情況,可以按深度來劃分統計。
詳見代碼


代碼

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章