1. 程式人生 > 其它 >The 15th Chinese Northeast Collegiate Programming Contest D - Vertex Deletion (樹形dp)

The 15th Chinese Northeast Collegiate Programming Contest D - Vertex Deletion (樹形dp)

題意:
給定一個由\(n\)個點組成的樹,然後每次可以從樹中選擇一個頂點刪去,定義操作結束後是完美的,但且僅當刪點後形成的點集中,每個點都有至少一個點與它相連,問一共有多少種刪點方式能夠形成完美的局面,答案模\(998244353\)

思路:
統計方案類的問題,可以往\(dp\)方向靠,又是在樹上,那麼就是樹形\(dp\)了。
題目要求都必須有節點相連才是合法轉態且又是否刪除這個操作選項,那就考慮狀態預設的時候要帶上節點連線子節點情況的資訊和是否刪除的資訊,即可用三個狀態來囊括。
考慮令
\(dp_{u,0}\)表示當前點刪除後能形成的方案數。
\(dp_{u,1}\)表示當前點保留且至少有一個子節點與它相連形成的方案數。
\(dp_{u,2}\)

表示當前點保留且沒有任何一個子節與它相連的方案數

按照如下方式轉移即可。
特別注意第三種轉移,會計算到當前點的所有兒子都取刪除的這一種情況,不符合題意,應當刪去這種情況,至少留有一個子節點,不確定留那個,只能這樣用\(size(u)\)個和式相乘轉移,乘起來拆開後之後就會是一些包含\(dp[v][0]\)存在\([0,size(u)]\)次的式子,很明我們只需要把存在\(size(u)\)的答案刪去即為至少存在一個子節點的答案。

從節點\(1\)開始遍歷,可知最終答案即為\(dp[1][0]+dp[1][1]\)

#include <bits/stdc++.h>

using namespace std;

#define pb push_back
#define eb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int N = 1e5 + 10;
const ll MOD = 998244353;
int n,siz[N];
std::vector<int>G[N];
ll f[N][3];
//0/1/2記錄當前u點的三種狀態 0代表刪除 1代表保留且該點至少與一個子節點相連 2代表保留且不與任意一個子節點相連
void dfs(int u,int fa) {
	f[u][0] = f[u][1] = f[u][2] = 1;
	for(auto v : G[u]) {
		if(v == fa) continue;
		dfs(v,u);
		f[u][0] = f[u][0] * (f[v][0] + f[v][1]) % MOD;
		f[u][2] = f[u][2] * f[v][0] % MOD;
		f[u][1] = f[u][1] * (f[v][0] + f[v][1] + f[v][2]) % MOD; 
	}
	//此時f[u][1]會包含著一種 所有點都不與之相連的狀態 所以需要去掉這個狀態
	f[u][1] = (f[u][1] - f[u][2] + MOD) % MOD;
}

void solve() {
	scanf("%d",&n);
	for(int i = 1;i <= n;i ++) G[i].clear();
	for(int i = 1,u,v;i < n;i ++) {
		scanf("%d%d",&u,&v);
		G[u].pb(v),G[v].pb(u);
	}
	dfs(1,-1);
	ll ans = (f[1][0] + f[1][1]) % MOD;
	printf("%lld\n",ans);
}

int main() {
	int T;scanf("%d",&T);
	while(T--) solve();
	return 0;
}