4784: [Zjoi2017]仙人掌
Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 117 Solved: 73
[ Submit][ Status][ Discuss]
Description
如果一个无自环无重边无向连通图的任意一条边最多属于一个简单环,我们就称之为仙人掌。所谓简单环即不经过
重复的结点的环。
现在九条可怜手上有一张无自环无重边的无向连通图,但是她觉得这张图中的边数太少了,所以她想要在图上连上
一些新的边。同时为了方便的存储这张无向图,图中的边数又不能太多。经过权衡,她想要加边后得到的图为一棵
仙人掌。不难发现合法的加边方案有很多,可怜想要知道总共有多少不同的加边方案。两个加边方案是不同的当且
仅当一个方案中存在一条另一个方案中没有的边。
Input
多组数据,第一行输入一个整数T表示数据组数。
每组数据第一行输入两个整数n,m,表示图中的点数与边数。
接下来m行,每行两个整数u,v(1≤u,v≤n,u!=v)表示图中的一条边。保证输入的图
联通且没有自环与重边
Sigma(n)<=5*10^5,m<=10^6,1<=m<=n*(n-1)/2
Output
对于每组数据,输出一个整数表示方案数,当然方案数可能很大,请对998244353取模后
输出。
Sample Input
2
3 2
1 2
1 3
5 4
1 2
2 3
2 4
1 5
3 2
1 2
1 3
5 4
1 2
2 3
2 4
1 5
Sample Output
2
8
对于第一组样例合法加边的方案有 {}, {(2,3)},共 2 种。
8
对于第一组样例合法加边的方案有 {}, {(2,3)},共 2 种。
HINT
Source
对于原图,进行这样的简单操作:使用tarjan算法找出所有简单环
每找到一个环,就改变它的结构,从中任选一个点为该环的根
其它点连在这个点下面,环外和环右边相连的边不改变
这样每个环都变成只剩n - 1条边,最终能将这个仙人掌改造成一棵树
不妨将所有本来属于环上的点染成黑色,每一条新加的边可以对应成新树上的一条反向边
新加的边除了要满足仙人掌的性质,还要满足不能经过任何黑点之间的连边
此时这些黑点就将原树划分为一些连通块,只要统计每个块之间的答案,用乘法原理乘起来就行了
定义状态f[i]:以i为根的子树构成仙人掌的方案数
对于每一种最终合法的方案,只要其中存在一条边不在任何简单环上,
就增加一条它的重边,显然,这样仍然符合仙人掌的性质。
统计以x为根的子树的方案时,考虑每个儿子y,y向x连的这条边
这条边可以与另外一个儿子配对,或者和x的这条边配对
于是统计好子树答案,用乘法原理和组合数简单地转移就行了
注意当统计到某个连通块的根的时候,由于根向上的这条边不能用,是不能把根算进去的
原题好像是还需要特判无解的情况。。利用tarjan的性质搞搞就行啦
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<bitset>
#define min(a,b) ((a) < (b) ? (a) : (b))
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;
const int maxn = 5E5 + 50;
typedef long long LL;
const LL mo = 998244353;
int n,m,dfs_clock,Ans,dfn[maxn],low[maxn],f[maxn],h[maxn],Fac[maxn],Inv[maxn];
bool vis[maxn],Mark[maxn],Calc[maxn];
stack <int> s;
vector <int> v[maxn],g[maxn];
inline int Mul(const LL &x,const LL &y) {return x * y % mo;}
inline int Add(const int &x,const int &y) {return x + y < mo ? x + y : x + y - mo;}
inline int C(const int &N,const int &M) {return Mul(Fac[N],Mul(Inv[M],Inv[N - M]));}
int getint()
{
char ch = getchar(); int ret = 0;
while (ch < '0' || '9' < ch) ch = getchar();
while ('0' <= ch && ch <= '9')
ret = ret * 10 + ch - '0',ch = getchar();
return ret;
}
int ksm(int x,int y)
{
int ret = 1;
for (; y; y >>= 1)
{
if (y & 1) ret = Mul(ret,x);
x = Mul(x,x);
}
return ret;
}
bool Dfs1(int x,int from)
{
int cnt = 0; vis[x] = 1;
dfn[x] = low[x] = ++dfs_clock; s.push(x);
for (int i = 0; i < v[x].size(); i++)
{
int to = v[x][i];
if (to == from) continue;
if (!vis[to])
{
if (!Dfs1(to,x)) return 0;
if (low[to] == dfn[x])
{
for (;;)
{
int k = s.top(); if (k == x) break;
s.pop(); Calc[k] = Mark[k] = 1; g[x].push_back(k);
}
}
else if (low[to] > dfn[x]) g[x].push_back(to),s.pop();
else low[x] = min(low[x],low[to]),++cnt;
}
else
{
if (dfn[to] < dfn[x]) ++cnt;
low[x] = min(low[x],dfn[to]);
}
}
return cnt < 2;
}
void Dfs2(int x)
{
int Son = 0,tot = 1;
for (int i = 0; i < g[x].size(); i++)
{
int to = g[x][i];
Dfs2(to); if (Mark[to]) continue;
++Son; tot = Mul(tot,f[to]);
}
if (!Son) {f[x] = 1; return;} f[x] = 0;
if (Calc[x])
{
for (int i = 0; (i << 1) <= Son; i++)
f[x] = Add(f[x],Mul(tot,Mul(C(Son,i << 1),h[i << 1])));
Ans = Mul(Ans,f[x]);
}
else
{
++Son;
for (int i = 0; (i << 1) <= Son; i++)
f[x] = Add(f[x],Mul(tot,Mul(C(Son,i << 1),h[i << 1])));
}
}
void Solve()
{
n = getint(); m = getint();
while (m--)
{
int x = getint(),y = getint();
v[x].push_back(y); v[y].push_back(x);
}
if (!Dfs1(1,-1)) {puts("0"); return;}
Ans = Calc[1] = 1; Dfs2(1); printf("%d\n",Ans);
}
void Clear()
{
for (int i = 1; i <= n; i++)
{
v[i].clear(),g[i].clear();
vis[i] = Mark[i] = Calc[i] = 0;
}
dfs_clock = 0; while (!s.empty()) s.pop();
}
int main()
{
#ifdef DMC
//freopen("ex_cactus2.in","r",stdin);
freopen("DMC.txt","r",stdin);
#endif
int T = getint(); Fac[0] = h[0] = 1;
for (int i = 1; i < maxn; i++) Fac[i] = Mul(Fac[i - 1],i);
Inv[maxn - 1] = ksm(Fac[maxn - 1],mo - 2);
for (int i = maxn - 2; i >= 0; i--) Inv[i] = Mul(Inv[i + 1],i + 1);
for (int i = 2; i < maxn; i += 2) h[i] = Mul(h[i - 2],i - 1);
while (T--) Solve(),Clear();
return 0;
}