题面
https://www.luogu.org/problem/P3687
题解
如果原图不是仙人掌(在这里,我们认为一棵树也是一个仙人掌),则无解,输出$0$。
如果原图是一个仙人掌,加的边是不能跨越仙人掌上的环边的(若跨越,则仙人掌上的环边被$2$个环所有,不是仙人掌),所以我们直接把环边断掉,变成一个森林,直接统计每棵树的答案,最后乘法原理乘起来就可以了。
首先,很自然的想到,令$F[i]$表示$i$节点的子树及它的父边形成一个仙人掌的方案数(父边可选可不选),这个状态不同寻常,一般我们的状态都是不带父边的。但是这道题不一样,稍微分析一下就可以得到这个状态。(这个应该不是难点吧)
如果是按寻常的方法转移,这个状态是有问题的,因为要考虑儿子之间可能还有边,我的解决方法是分父边一定被覆盖和父边一定没有被覆盖两个状态讨论,这样只能做到$O(\sum{deg^2})$。
题解很神奇,先预处理出一个数组$h[i]$,表示有$i$个点,他们之间互相匹配的方案数,显然,分$i$不配对还是和别人配对,有$h[i]=h[i-1]+(i-1)h[i-2]$(吐槽:我觉得这个数组只是处理出了特殊情况,如果一个点$x$,对于它的每个儿子$y$都有$f[y]=1$,是可以这么转移的,但是$f[y]$可以是任意值,就假了啊),然后是一个神奇的结论(特殊情况得到一般结论?)有$$f[x]=\prod_{(x,y) \in E}{f[y]} \times g[deg[x]]$$,看了题解之后,我的脑子里也预想过这个结论,但是不会证明(大概是乘法原理对应每一种情况吧),不管了,既然结论这么好记,我就记下了行吧。
问题在于,我无法给出一种构造方法,使得不知道$y$的父边有没有选上的情况和$y'$配对,有大佬知道吗$qwq$?
upd2019.8.30:当$y$的父边没有被选上时,链的一个端点就是$y$,当$y$的父边已被选上时,这条链一定向下指向一个$y$的子树中(不包含$y$)的点,以它作为链的端点,并把原来的链删去。对于$x$的父边,如果有对象,就拿$fa[x]$连,如果没有,就空着。
至于这样为什么是不重不漏的,让我再想想。
写这道题的时候调试了很长时间,最后发现只是一个后来加的$s.pop()$忘了把$ins[y]$赋值为$0$,这种粗心的毛病已经不是第一次了,每次都会因此浪费很长时间,要是在考试时遇到,估计心态就崩了吧。
#include<stack> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 500500 #define M 1000500 #define ri register int #define mod 998244353 #define LL long long using namespace std; inline int read() { int ret=0; char ch=getchar(); while (ch<'0' || ch>'9') ch=getchar(); while (ch>='0' && ch<='9') ret*=10,ret+=(ch-'0'),ch=getchar(); return ret; } int f[N],g[N],cloock,fl=0; int dfn[N],low[N]; int to[2*M],plc[N]; vector<int> ed[N],tr[N]; bool fib[2*M],ins[N]; stack<int> s; void del(int e) {fib[e]=fib[1^e]=1; } void tarjan(int x) { dfn[x]=low[x]=++cloock; s.push(x); plc[x]=s.size(); ins[x]=1; for (ri i=0;i<ed[x].size();i++) { int e=ed[x][i]; int y=to[e]; if (!dfn[y]) { tarjan(y); low[x]=min(low[x],low[y]); if (low[y]>=dfn[x]) { if (s.top()==y) { s.pop(); ins[y]=0; continue; } del(e); int pre=x,tail=s.top(); while (1) { int t=s.top(); s.pop(); ins[t]=0; int suc=(t==y)?(x):(s.top()); for (ri j=0;j<ed[t].size();j++) { int r=to[ed[t][j]]; if (ins[r] && (plc[r]>=plc[y] || r==x)) { if (r!=suc && r!=pre && !(t==tail && r==x)) { if (fl==0) puts("0"); fl=1; } } if (r==pre) del(ed[t][j]); } pre=t; if (t==y) break; } } } else { low[x]=min(low[x],dfn[y]); } } } int dp(int x,int ff) { f[x]=1; for (ri i=0;i<tr[x].size();i++) { int y=tr[x][i]; if (y==ff) continue; dp(y,x); f[x]=(f[x]*1LL*f[y])%mod; } f[x]=(f[x]*1LL*g[tr[x].size()])%mod; return f[x]; } int main() { //freopen("4.in","r",stdin); //freopen("4.out","w",stdout); g[0]=g[1]=1; for (ri i=2;i<N;i++) g[i]=(g[i-1]+(g[i-2]*1LL*(i-1))%mod)%mod; int T=read(); while (T--) { cloock=0; int n=read(),m=read(); for (ri i=1;i<=n;i++) ed[i].clear(),tr[i].clear(); for (ri i=1;i<=n;i++) ins[i]=0; while (!s.empty()) ins[s.top()]=0,s.pop(); for (ri i=1;i<=n;i++) dfn[i]=low[i]=0; int cntb=-1; for (ri i=1;i<=m;i++) { int u=read(),v=read(); ed[u].push_back(++cntb); to[cntb]=v; fib[cntb]=0; ed[v].push_back(++cntb); to[cntb]=u; fib[cntb]=0; } fl=0; for (ri i=1;i<=n;i++) if (!dfn[i]) { tarjan(i); while (!s.empty()) ins[s.top()]=0,s.pop(); } if (fl) continue; for (ri i=0;i<=cntb;i++) if (!fib[i]) { tr[to[i^1]].push_back(to[i]); } for (ri i=1;i<=n;i++) f[i]=0; LL ans=1; for (ri i=1;i<=n;i++) if (!f[i]) ans*=dp(i,-1),ans%=mod; printf("%d\n",ans); } return 0; }