T1 仙人掌
题目
题目描述
如果一个无自环无重边无向连通图的任意一条边最多属于一个简单环,我们就称之为仙人掌。所谓简单环即不经过重复的结点的环。
现在九条可怜手上有一张无自环无重边的无向连通图,但是她觉得这张图中的边数太少了,所以她想要在图上连上一些新的边。同时为了方便的存储这张无向图,图中的边数又不能太多。经过权衡,她想要加边后得到的图为一棵仙人掌。
不难发现合法的加边方案有很多,可怜想要知道总共有多少不同的加边方案。
两个加边方案是不同的当且仅当一个方案中存在一条另一个方案中没有的边。
输入输出格式
输入格式:
多组数据,第一行输入一个整数 T 表示数据组数。
每组数据第一行输入两个整数 n ,m ,表示图中的点数与边数。
接下来 m 行,每行两个整数 u ,v (1 ≤ u ,v ≤ n ,u ≠ v ) 表示图中的一条边。保证输入的图联通且没有自环与重边。
输出格式:
对于每组数据,输出一个整数表示方案数,当然方案数可能很大,请对 998244353 取模后输出。
输入输出样例
输入样例#1:
2
3 2
1 2
1 3
5 4
1 2
2 3
2 4
1 5
输出样例#1:
2
8
输入输出样例#2:
http://pan.baidu.com/s/1kUIXmar
样例说明
对于第一组样例合法加边的方案有 {},{(2,3)},共 2 种。
时空限制
时间限制1s,空间限制512M。
数据范围
题解
首先,对于一条链,可以通过打表观察发现答案为 2n−1 2 n − 1
然后,我们考虑是一棵树的情况。我们可以将问题进行一定的转化,在树上加一条边,就等价于在树边上放了一条从边的一个端点到另一个端点的路径,而仙人掌要求没有重边自环且每条边至多属于一个环,就等价于要求这些路径长度至少为2且互不重合,问题就等价于用若干条路径去覆盖这棵树且满足上面条件的方案数。
对于求方案数,我们可以很容易的想到用DP来解决。
令 f(i) f ( i ) 为以 i 为根的子树有一条从 i 子树内部(不包括第 i 个节点)出发连向外部的边的方案数, g(i) g ( i ) 则为没有这样的边的方案数。直接DP的话不好转移,需要 O(n2) O ( n 2 ) 的时间复杂度,因为要知道 i 的孩子中有多少个有边延伸出来,才能计算把这些边配对的方案数。可以使用分治FFT优化这一部分的时间复杂度,即把每一个节点看成 f(i)x+g(i) f ( i ) x + g ( i ) ,然后把所有孩子相乘,能做到 O(nlog2n) O ( n l o g 2 n ) 。
直接DP不好转移,我们可以换一个方式。令 h(i) h ( i ) 表示将i个儿子进行配对的方案数,则 h(i)=h(i−1)+h(i−2)∗(i−1) h ( i ) = h ( i − 1 ) + h ( i − 2 ) ∗ ( i − 1 ) ,理解:对于第i个儿子的边,可以不和之前任何边进行配对,也可以和之前的任意一条边进行配对。设i的儿子数量为x,此时, g(i)=∏f(son(i))∗h(x) g ( i ) = ∏ f ( s o n ( i ) ) ∗ h ( x ) , f(i)=g(i)+∏f(son(i))∗h(x−1)∗x f ( i ) = g ( i ) + ∏ f ( s o n ( i ) ) ∗ h ( x − 1 ) ∗ x ,理解:i与父亲相连的那条边可以不选,也可以和任何一条与儿子相连的边相连。这样,就可以在 O(n) O ( n ) 时间下算出答案。
对于一张任意的图,分两种情况讨论:
(1)原图存在一条边同时属于两个环,此时需要特殊判断,并直接输出0。
(2)对于满足仙人掌条件的图,我们可以容易的发现环对于答案没有任何贡献,因此可以用Tarjan找出所有的环,然后将环中的边删去,这张图将会变成一个森林。对于森林中的每棵树,我们按照之前的方法求出答案,然后再用乘法原理进行累加就是原图的答案。
代码
#include<cstdio>
#include<iostream>
#include<cctype>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
#define LL long long
using namespace std;
const int maxn=500005,maxm=1000005,mo=998244353;
int T,n,m,x,y;
struct graph
{
int to,next,vis;
}a[maxm<<1];
int first[maxn],other[maxm<<1],np=0;
void read(int &x)
{
x=0;int F=0;char ch;ch=getchar();
while(!isdigit(ch)) {
if(ch=='-') F=1;ch=getchar();}
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
if(F) x=-x;return;
}
void add(int x,int y)
{
a[++np]=(graph){y,first[x],0};
first[x]=np;other[np]=np+1;
a[++np]=(graph){x,first[y],0};
first[y]=np;other[np]=np-1;
return;
}
int vis[maxn],dfn[maxn],low[maxn],dfs_clock=0,ebc=0;
int s[maxn],top=0,isc=1;
void DFS(int i,int fd)
{
vis[i]=1;dfn[i]=low[i]=++dfs_clock;s[++top]=i;int ok=0;
for(int j=first[i];j;j=a[j].next)
{
if(vis[a[j].to])
{
if(dfn[a[j].to]<dfn[i] && other[j]!=fd) low[i]=min(low[i],dfn[a[j].to]);
if(dfn[a[j].to]<dfn[i] && other[j]!=fd) {
if(ok) isc=0;else ok=1;a[j].vis=a[other[j]].vis=1;}
continue;
}
DFS(a[j].to,j);low[i]=min(low[i],low[a[j].to]);
if(low[a[j].to]<dfn[i]) {
if(ok) isc=0;else ok=1;}
}
if(low[i]==dfn[i])
{
ebc++;
while(1) {top--;if(s[top+1]==i) break;}
}
else a[fd].vis=a[other[fd]].vis=1;
return;
}
int check[maxn];
LL f[maxn],g[maxn],h[maxn],ans=1;
void _DFS(int i,int fa)
{
check[i]=1;LL pig=1;int child=0;
for(int j=first[i];j;j=a[j].next)
if(a[j].to!=fa && !a[j].vis)
{
_DFS(a[j].to,i);child++;
pig=pig*g[a[j].to]%mo;
}
f[i]=pig*h[child]%mo;g[i]=f[i];
if(child) g[i]=(g[i]+pig*h[child-1]%mo*child)%mo;
return;
}
void solve()
{
read(T);h[0]=1;h[1]=1;
for(int i=2;i<=500000;i++) h[i]=(h[i-1]+h[i-2]*(i-1))%mo;
while(T--)
{
read(n);read(m);
for(int i=1;i<=m;i++) read(x),read(y),add(x,y);
DFS(1,0);if(!isc) {
printf("0\n");goto A;}
for(int i=1;i<=n;i++