LGP3687口胡
神仙题。。。这就是ZJOI吗。。。
首先如果图是非仙人掌,答案一定为 \(0\)。如何判断非仙人掌?删掉所有点双的边,然后数一下点双中的点和被删掉的边。
来考虑仙人掌的情况。仙人掌的环将整个图分成了若干颗树,在环上连边是没有意义的。所以只需要考虑树即可。
考虑意见很许可但是很重要的事情:将一棵树填充成仙人掌时,将不在环上的边强制连接一条重边,变成一个环。
这样一来每条边就在且仅在一个环中了,考虑起来更加方便。
这样一个复杂的东西先考虑 DP。设 \(dp[u]\) 表示 \(u\) 子树内的合法方案数量,且 \(u\) 有一条连向 \(f[u]\) 的还没有拼接起来的边。
很明显一件事,让儿子们xjb匹配,也可以不匹配。然后自身选择一个儿子与其匹配或不匹配。\(u\) 为根节点时没有后面这个匹配。
设 \(g[n]\) 表示 \(n\) 个节点的菊花图xjb匹配的方案数。转移时考虑 \(n\) 是否要和前面的匹配,有 \(g[n]=g[n-1]+g[n-2]\times(n-1)\)。
明显有 \(dp[u]=\prod_{v\in son(u)}dp[v]\times g[|son(u)|+1]\) 什么的东西,反正是可以 \(O(n+m)\) 了。
#include<cstdio>
#include<cctype>
typedef unsigned ui;
const ui M=5e5+5,mod=998244353;
ui T,n,m,cnt,g[M],f[M],d[M],D[M],h[M];ui dfc,dfn[M],low[M];bool vis[M];
char _input[1<<26|1],*_p1=_input;
struct Edge{
ui v,nx;
}e[M<<2];
inline void Add(const ui&u,const ui&v){
e[++cnt]=(Edge){v,h[u]};h[u]=cnt;
e[++cnt]=(Edge){u,h[v]};h[v]=cnt;
}
inline ui min(const ui&a,const ui&b){
return a>b?b:a;
}
inline void Tarjan(const ui&u){
dfn[u]=low[u]=++dfc;d[u]=d[f[u]]+1;
for(ui v,E=h[u];E;E=e[E].nx)if((v=e[E].v)^f[u]){
if(!dfn[v])f[v]=u,Tarjan(v),low[u]=min(low[u],low[v]);
else low[u]=min(low[u],dfn[v]);
if(dfn[u]<low[v])++D[u],++D[v];
}
}
inline ui read(){
ui n(0);char s;while(!isdigit(s=*_p1++));while(n=n*10+(s&15),isdigit(s=*_p1++));return n;
}
signed main(){
fread(_input,1,1<<26,stdin);
T=read();g[0]=1;g[1]=1;
for(ui i=2;i<=500000;++i)g[i]=(g[i-1]+(i-1ull)*g[i-2])%mod;
while(T--){
ui ans(1);bool typ(false);n=read();m=read();cnt=1;dfc=0;
for(ui i=1;i<=n;++i)f[i]=d[i]=D[i]=h[i]=dfn[i]=low[i]=0,vis[i]=false;
for(ui u,v,i=1;i<=m;++i)u=read(),v=read(),Add(u,v);Tarjan(1);
for(ui i=1;i<=m;++i){
ui u=e[i<<1].v,v=e[i<<1|1].v;
if(f[u]==v||f[v]==u)continue;if(d[u]<d[v])u^=v^=u^=v;
while(d[u]^d[v])typ|=vis[u],vis[u]=true,u=f[u];if(typ)break;
while(u^v)typ|=vis[u]|vis[v],vis[u]=vis[v]=true,u=f[u],v=f[v];if(typ)break;
}
if(typ){
printf("0\n");continue;
}
for(ui u=1;u<=n;++u)ans=1ull*ans*g[D[u]]%mod;printf("%u\n",ans);
}
}