测试地址:仙人掌
做法:本题需要用到仙人掌判定+树形DP。
首先如果原图就不是仙人掌了,那么再怎么加边肯定也成不了仙人掌,所以我们应该先判断原图是不是仙人掌。判定方法是:对于原图求出DFS树,对于所有不在DFS树中的边,连接的两点一定具有祖孙关系,它会覆盖中间这一段的路径,如果一条边被覆盖一次以上,就表示这条边处于两个或更多个简单环中,不符合仙人掌的定义,树上差分判断一下即可。
特判掉不是仙人掌的情况后,显然图中的环对答案没有贡献,所以我们把原图中的环全部删掉,然后留下了若干棵树,因为这些树在原图中实际上是连通的,如果在不同的树之间加边就不符合仙人掌的性质,所以我们只需要分别考虑这些树上加边的方案,然后乘法原理乘起来即可。
思考一下之后我们发现,往一棵树中加边的方案数,等价于在树中取若干条长度大于等于的边不相交的路径的方案数。之所以是长度大于等于,是因为本题中仙人掌的定义和平常略有不同——没有重边,所以你不能加一条重边上去。考虑一棵子树的方案,我们发现它会对上面产生影响的,是这样的一些方案:要么在这棵子树中已经自给自足,要么有一条长度为的路径在根上等待上面的边的加入。所以我们把根为的子树中自给自足的方案数设为,会对上面产生影响的方案数为,考虑如何转移。
对于自给自足的方案,我们实际上是在根到儿子的边里做一个匹配,即选定几对儿子,并对于每一对儿子,将两个儿子的方案连起来,那么令为个点做匹配的方案数,我们知道:
边界条件为。若点不和其他点做匹配,方案数为,否则和一个其他点做匹配,有种方案,其他点做匹配的方案数为,所以得到上式。
因为上面我们说了,在根上的状态转移就是做匹配,进一步地我们有:
其中为的每一个儿子,为的儿子个数。
那么怎么计算?里有一部分是,另一部分就是有一条长度为的路径等待上面边加入的方案数了。我们选定这个长度为的路径是在根与某一个儿子之间,对于每一个儿子都产生的方案数,因此我们有:
这样我们就得到了状态转移方程,注意最底层的节点中,因为它们没有儿子所以直接求会溢出。还要注意BZOJ上没有但是原题面中有的一个非常重要的提示:因为可能较大,注意初始化的时间复杂度。如果你用memset的话铁定TLE,必须用for循环初始化。
这样我们就完成了这一题,时间复杂度为。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
int T,n,m;
int first[500010],tot,dep[500010],cnt[500010];
ll f[500010],g[500010],h[500010],ans;
bool visp[500010],vis[1000010],flag;
struct edge
{
int v,next,id;
}e[2000010];
void insert(int a,int b,int id)
{
e[++tot].v=b;
e[tot].next=first[a];
e[tot].id=id;
first[a]=tot;
}
int dfs(int v,int laste)
{
int totcnt=0;
visp[v]=1;
for(int i=first[v];i;i=e[i].next)
if (e[i].id!=laste)
{
if (!visp[e[i].v])
{
vis[e[i].id]=1;
dep[e[i].v]=dep[v]+1;
totcnt+=dfs(e[i].v,e[i].id);
}
else if (dep[e[i].v]<dep[v])
cnt[v]++,cnt[e[i].v]--;
}
totcnt+=cnt[v];
if (totcnt==1) vis[laste]=0;
if (totcnt>1) flag=1;
return totcnt;
}
void dp(int v,int fa)
{
visp[v]=1;
ll prod=1,son=0;
for(int i=first[v];i;i=e[i].next)
if (vis[e[i].id]&&e[i].v!=fa)
{
dp(e[i].v,v);
prod=prod*g[e[i].v]%mod;
son++;
}
f[v]=h[son]*prod%mod;
if (son) g[v]=(f[v]+son*h[son-1]%mod*prod%mod)%mod;
else g[v]=f[v];
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
first[i]=0;
tot=0;
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
insert(a,b,i),insert(b,a,i);
}
dep[1]=0;
for(int i=1;i<=m;i++)
vis[i]=0;
for(int i=1;i<=n;i++)
visp[i]=cnt[i]=0;
flag=0;
dfs(1,0);
if (flag) {printf("0
");continue;}
h[0]=h[1]=1;
for(ll i=2;i<=n;i++)
h[i]=(h[i-1]+(i-1)*h[i-2]%mod)%mod;
for(int i=1;i<=n;i++)
visp[i]=0;
ans=1;
for(int i=1;i<=n;i++)
if (!visp[i]) dp(i,0),ans=ans*f[i]%mod;
printf("%lld
",ans);
}
return 0;
}