codeforce 1332 F. Independent Set(樹形 dp,樹上計數問題)

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述


題目大意:給一棵樹,求這棵樹的所有邊生成子圖的所有獨立集數,邊生成子圖的定義爲:原圖拿條0條或多條邊並刪掉孤立點(沒有和其它點連邊的點)的圖。


寫過的爲數不多的樹上計數問題:

我的做法將狀態分爲五種:dp[u][0/1][0/1]dp[u][0/1][0/1]res[u]res[u],分別表示:
dp[u][0/1][0/1]:以 u 爲根的子樹中,u 點在邊生成子圖中(u 點可以孤立,其它點滿足邊生成子圖定義,允許 u 點孤立是爲了合法解的轉移),u 點有連子樹 / 沒連子樹, u 點被選爲獨立集中的一點 / u 點不被選爲獨立集中的一點的方案數
res[u]:以 u 爲根的子樹中,u 點不在邊生成子圖中的方案數(顯然 u 點不能選爲獨立集,也不能向兒子連邊,所以把這個狀態單獨拿出來)

初始化及轉移方程:
初始化:res[u] = dp[u][0][0] = dp[u][0][1] = 1res[u] = 1 代表空子圖的方案數,這種狀態的答案在子樹合併考慮第 i 棵子樹時需要用到,dp[u][0][0],dp[u][0][1] 可以賦值爲 1,因爲狀態中定義了 uu 點可以孤立。

res[u]:由於子樹間的方案獨立,用子樹合併的方式,將子樹中的所有合法解乘起來就得到 res[u]res[u],子樹 v 的合法解爲:dp[v][1][0] + dp[v][1][1] + res[v];很容易理解:v 點不包括在子圖的方案數 + v 點包括在子圖中的方案, v 點包括在子圖中的方案裏 v 不能孤立,因此要去掉 dp[v][0][0/1]

dp[u][0][0/1]:轉移方程顯然和 res[u] 相同,因爲 u 點獨立,u 是否作爲獨立集中的一點不會影響子樹的合法解。

dp[u][1][0]:由於要和兒子連邊,若 u 已經和前 i 個兒子有連邊,則當前子兒子枚舉連和不連進行轉移,這種情況方案數爲:dp[u][1][0] * (sum[v] + (dp[v][1][0] + dp[v][1][1] + res[v])),sum[v] 是 dp[v] 的四種狀態之和,sum[v] 是連這個兒子的方案,(dp[v][1][0] + dp[v][1][1] + res[v]) 是不連這個兒子的方案(也就是這個兒子的合法方案),類似的,如果 u 沒有和前 i 個兒子連過邊,則當前兒子一定要連邊,方案數爲:dp[u][0][0] * sum[v]

dp[u][1][1]:和 dp[u][1][0] 類似,如果連邊,因爲 uu 點已選爲獨立集中的一點, vv 點則不能作爲獨立集中的一點,將 sum[v] 修改爲 dp[v][0][0] + dp[v][1][0]

最後總答案 ans = dp[1][1][0] + dp[1][1][1] + res[1] - 1,扣掉1 扣掉的是空子圖的方案,在這題中空子圖不能作爲一種合法解。


代碼:

#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
typedef long long ll;
const int maxn = 3e5 + 10;
int n;
vector<int> g[maxn];
ll fpow(ll a,ll b) {
	ll r = 1;
	while (b) {
		if (b & 1) r = r * a % mod;
		b >>= 1;
		a = a * a % mod;
	}
	return r;
}
ll dp[maxn][2][2],res[maxn],tp[maxn][2][2],sum[maxn],ans[maxn];
void dfs(int u,int fa) {
	// dp[u][0/1][0/1]:以 u 爲根的子樹中,u 點比存在的子圖的答案 
	// res[u] :以 u 爲根的子樹中,u 點不存在的子圖的答案 
	res[u] = 1;			// 代表空集,也作爲一種答案 
	dp[u][0][0] = dp[u][0][1] = 1;	// 只有自己一個點 
	sum[u] = 0;
	for (auto it : g[u]) {
		if (it == fa) continue;
		dfs(it,u);
		res[u] = (res[u] * (dp[it][1][0] + dp[it][1][1] + res[it]) % mod + mod) % mod;
		for (int i = 0; i < 2; i++)			
			for (int j = 0; j < 2; j++)			 
				tp[u][i][j] = dp[u][i][j];
		for (int i = 0; i < 2; i++) {				//選不選兒子 
			for (int j = 0; j < 2; j++) {			//u點要不要選 
				if (i == 0) {
					tp[u][i][j] = (dp[u][i][j] * (dp[it][1][0] + dp[it][1][1] + res[it]) % mod) % mod;
				} else {
					if (j == 0) {
						tp[u][i][j] = (dp[u][i][j] * (sum[it] + (dp[it][1][0] + dp[it][1][1] + res[it])) % mod + 
										dp[u][0][j] * sum[it] % mod) % mod;
					} else {
						tp[u][i][j] = (dp[u][i][j] * (dp[it][0][0] + dp[it][1][0] + dp[it][1][0] + dp[it][1][1] + res[it]) % mod + 
										dp[u][0][j] * (dp[it][0][0] + dp[it][1][0]) % mod) % mod;
					}
				}
			}
		}
		for (int i = 0; i < 2; i++)			
			for (int j = 0; j < 2; j++)			 
				dp[u][i][j] = tp[u][i][j];		
	}
	for (int i = 0; i < 2; i++)
		for (int j = 0; j < 2; j++)
			sum[u] = (sum[u] + dp[u][i][j]) % mod;
}
int main() {
	scanf("%d",&n);
	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);
	}
	dfs(1,0);
	printf("%lld\n",(dp[1][1][0] + dp[1][1][1] + res[1] - 1 + mod) % mod);			//減一是減掉空集
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章