题意:给定一张无向简单图,同时规定一条边只属于一个环。可以删除任意条边使得这张图变成森林,也就是使得每一个连通块都是树。求一共有多少种方案。
#include <cstdio> #include <cstring> #include <string> #include <cmath> #include <iostream> #include <algorithm> #include <vector> #include <stack> #include <sstream> #include <map> #include <set> #include <queue> #include <stdlib.h> /* 用 DFS 暴力找环,然后乘法原理算一下即可。注意非环边也会有贡献。 DFS 可以模仿 Tarjan 算法(离线算法)写。 */ typedef long long LL; const int maxn = 3e5 + 10; using namespace std; int u, v, n, m, path[maxn], vis[maxn]; LL ans = 1; LL cnt, mod = 998244353; LL sum = 0; //sum是环的节点一共有多少个 vector<int> g[maxn]; LL qpow(LL n) { LL a=2; LL re=1; while(n) { if(n&1) //判断n的二进制最后一位是不是1,或者说判断n是不是奇数 { re=(re*a)%mod; } n>>=1; //除掉n二进制的最后一位 a=(a*a)%mod; } return re%mod; } void dfs(int now, int pre) { vis[now] = 1;//将当前这个点标记为遍历过 for (int i = 0; i < g[now].size(); ++i) //将与now相连的点全都遍历一遍 { int to = g[now][i]; if (to == pre) continue; //如果to是自己的父亲节点(就是已经遍历过了),那么直接continue就可以了。 if (vis[to] == 0) //如果to并没有被遍历,那么对to这个节点进行遍历 { path[to] = now; dfs(to, now); } else if (vis[to] == 2) continue; //如果这个点已经被遍历两次了 else //如果vis[to]==1,也就是to这个节点已经被遍历过了,所以形成环(比如1,2,3之间两两相连,1-》2,2-〉3,3-》1) { int temp = now; //设temp为当前这个点 cnt = 1; //cnt为最后成环的个数 while (temp != to) //如果temp不等于想要最后成环的点to,那么就寻找 { ++cnt; temp = path[temp]; } sum += cnt; //成环的点加上cnt ans = (ans * (qpow(cnt) - 1)) % mod; } } vis[now] = 2; //在最后将now这个节点设为2 } int main() { while(~scanf("%d %d", &n, &m)) //分别代表点和边 { memset(path, 0, sizeof(path)); memset(vis, 0, sizeof(vis)); for (int i=1;i<=m;i++) { g[i].clear(); } sum=0; ans=1; for (int i = 1; i <= m; ++i) //建立无向图 { scanf("%d %d", &u, &v); g[u].push_back(v); g[v].push_back(u); } for (int i = 1; i <= n; ++i) //一共n个点,对这n个点都进行遍历一遍 { if (vis[i] == 0) //如果这个点没有被遍历过,那么用dfs对这个点进行遍历一遍 { dfs(i, 0); } } printf("%lld\n", qpow(m - sum) * ans % mod); //qpow(m-sum)代表的是无环的点可以进行删掉的个数 } return 0; }