仙人掌
实际上我们只需要解决树的部分分即可。因为如果我们在加上边时,肯定无法加在原来的环上。
所以可以把所有环删除后再进行dp。实际上可以对每个连通块分别dp,答案是所有连通块dp结果的乘积。
用分治fft可以把时间复杂度优化到$O(nlog^2_2n)$,jzoj上有一道题是这道题的变体,那道题不需要转化模型,但是需要使用分治fft。
但是这道题用分治fft肯定是会超时的。
转化模型后,这道题的模型是“使用任意多条链(可以只包含一条边)覆盖整个图的方案数”。原题要求没有重边,但是可以“不用覆盖每条边”,所以可以把未被覆盖的边都加上一个自环,转变成前面所述的问题。
设g[i]表示有i个点,互相连边的方案,可以得到
g[n]=g[n-1]+g[n-2]*(n-1)。处理g时间复杂度O(n)
如果当前节点是根的话,则不能向外连边,那么再乘上g[儿子个数]即可(就是把儿子的方案组合在一起)u
否则当前点可以向外连边。把现在的节点算进来,就是要乘以g[儿子个数+1]
时间复杂度O(n)
#include<bits/stdc++.h> using namespace std; #define mo 998244353ll #define int long long #define N 1000010 int t,n,m,ec,h[N],v[N],fa[N],dfn[N],ct,nxt[N],x,y,f[N],ok,vi[N],d[N]; void add(int x,int y){v[++ec]=y;nxt[ec]=h[x];h[x]=ec;} void dfs(int x){ dfn[x]=++ct; for(int i=h[x];i;i=nxt[i]){ if(!dfn[v[i]]){ fa[v[i]]=x; dfs(v[i]); } else if(v[i]!=fa[x]&&dfn[v[i]]>dfn[x]){ d[x]-=2; for(int y=v[i];y!=x;y=fa[y]){ if(vi[y]){ ok=1; return; } vi[y]=1; d[y]-=2; } } if(ok)return; } } signed main(){ freopen("cactus.in","r",stdin); freopen("cactus.out","w",stdout); cin>>t; while(t--){ scanf("%lld%lld",&n,&m); ec=ok=0; for(int i=1;i<=n;i++) h[i]=dfn[i]=vi[i]=d[i]=0; while(m--){ int x,y; scanf("%lld%lld",&x,&y); add(x,y); add(y,x); d[x]++; d[y]++; } f[0]=f[1]=1; for(int i=2;i<=n;i++) f[i]=(f[i-1]+f[i-2]*(i-1)%mo)%mo; for(int i=1;i<=n;i++) if(!dfn[i])dfs(i); int ans=1; for(int i=1;i<=n;i++) ans=ans*f[d[i]]%mo; if(ok)ans=0; cout<<ans<<' '; } }
树状数组
如果打表,发现原代码维护的是后缀信息。
gg