题意:给定一张无向简单图,同时规定一条边只属于一个环。可以删除任意条边使得这张图变成森林,也就是使得每一个连通块都是树。求一共有多少种方案。
#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;
}



京公网安备 11010502036488号