題目大意:給一棵樹,求這棵樹的所有邊生成子圖的所有獨立集數,邊生成子圖的定義爲:原圖拿條0條或多條邊並刪掉孤立點(沒有和其它點連邊的點)的圖。
寫過的爲數不多的樹上計數問題:
我的做法將狀態分爲五種: 和 ,分別表示:
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] = 1
,res[u] = 1
代表空子圖的方案數,這種狀態的答案在子樹合併考慮第 i 棵子樹時需要用到,dp[u][0][0],dp[u][0][1]
可以賦值爲 1,因爲狀態中定義了 點可以孤立。
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] 類似,如果連邊,因爲 點已選爲獨立集中的一點, 點則不能作爲獨立集中的一點,將 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;
}