题目链接:https://codeforces.com/gym/102361/problem/F
题意
有 (n) 个点和 (m) 条边,每条边属于 (0) 或 (1) 个环,问去掉一些边使得图变为森林的方案个数。
题解
找出所有环的长度 (c_i),每个环可以去掉 (1,2,dots,c_i) 条边,方案各为 (C_{c_i}^1,C_{c_i}^2, dots C_{c_i}^{c_i}),即 (2^{c_i} - 1) 。
所有环的去边方案共有 (prod limits _{i = 1}^{c.size} 2^{c_i} - 1) 。
余下的边为 (m - sum limits _{i=1}^{c.size} c_i),可以去掉 (0,1,2,dots,m - sum limits _{i=1}^{c.size} c_i) 条边,方案总数为 (2^{m - sum limits _{i=1}^{c.size} c_i}) 。
所以总的方案个数即为:({2^{m - sum limits _{i=1}^{c.size} c_i}} imes ({prod limits _{i = 1}^{c.size} 2^{c_i} - 1})) 。
下面就是如何计算所有环的长度,利用 (dfs) 记录每个点的深度:
- 如果下一个点深度小于当前点且访问过,那么二者深度之差加一即为所在环的长度。
- 如果下一个点深度大于等于当前点,则该点之前已访问过且环的长度也已经计算过,可以跳过。
Tips
图可能不连通,所以需要对每个未访问过的点都进行 (dfs) 。
代码
#include <bits/stdc++.h>
using namespace std;
constexpr int MOD = 998244353;
int binpow(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = 1LL * res * a % MOD;
a = 1LL * a * a % MOD;
b >>= 1;
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<vector<int>> G(n);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
--u, --v;
G[u].push_back(v);
G[v].push_back(u);
}
vector<int> dep(n), cycle;
function<void(int, int)> dfs = [&](int u, int p) {
if (dep[u] != 0) {
if (dep[u] < dep[p])
cycle.push_back(dep[p] - dep[u] + 1);
} else {
dep[u] = (p == -1 ? 0 : dep[p]) + 1;
for (auto v : G[u]) {
if (v != p) {
dfs(v, u);
}
}
}
};
for (int i = 0; i < n; i++) {
if (dep[i] == 0) {
dfs(i, -1);
}
}
long long ans = binpow(2, m - accumulate(cycle.begin(), cycle.end(), 0));
for (auto i : cycle) {
ans *= binpow(2, i) - 1;
ans %= MOD;
}
cout << ans << "
";
return 0;
}