题解 CF512D 【Fox And Travelling】
cosmicAC
2020-06-05 15:49:42
这道题$n=100$还有三秒时限,所以大家纷纷使用$O(n^3)$算法通过了。下面我提供一种$O(n^2)$的算法。
首先,把输入的图求边双、缩点。如果一个边双有至少两个点,这个边双中的任何一个点就肯定不可能被遍历到(因为环里没有叶子)。所以,只需要考虑缩点之后的森林,其中有一些点不能选的情况下,遍历$k$个节点的方案数(因为现在可以选的点和原图的点是一一对应的)。
森林里的每棵树显然是独立的。由于可以自由组合,答案的EGF就是每棵树答案的EGF的卷积。所以我们只需要会求每一棵树的答案,就可以用$O(n^2)$的暴力卷积求出整个森林的答案。
考虑一棵树的答案是怎么求出来的。容易发现,最终被遍历到的点一定是这棵(**无根**)树的一些(两两不交的)子树。令$G_t$表示(**无根**)子树$t$答案的EGF,那么这棵树的EGF就等于
$$\sum_{S\textrm{是一些子树的集合}}\prod_{\textrm{子树}t\in S}G_t$$
乍一看挺对的?然而这个等式其实会重复计算一些方案。比如说第一个样例,序列$[1,3,2]$会在子树集合为$\{\{1,2\},\{3\}\}$时计算到,也会在子树集合为$\{\{1\},\{2,3\}\}$时计算到。
但是这个等式在绝大多数情况下都是对的。准确地说,**如果没有选中全部的点,就不会造成重复的计算**。所以分成两部分考虑:选中了这棵树中所有的点时对答案的贡献;没有全部选中时对答案的贡献。整棵树的答案就是把这两部分贡献加起来就行了。
选中了一棵树中全部的点的方案数
---
注意这里用的是“方案数”而不是EGF,因为点数已经被固定了。
考虑枚举**最后一个**遍历到的点。假设其为$p$。此时可以把$p$定为整棵树的根,题目的限制就变成了“**遍历一个点之前,必须遍历其子树中所有的点**”。可以发现这样统计是不重不漏的。
考虑dp。设$F_i$为以$p$为根时,$i$子树内的遍历方案数。由于各个子树之间是独立的,可以自由组合,并且$i$在其子树之内一定最后才被遍历,有一个简单的转移:
$$F_i=(siz_i-1)!\prod_{j\textrm{是}i\textrm{的儿子}}\frac{F_j}{siz_j!}$$
这里的$siz_i$是子树$i$的大小。当然,如果第$i$个点被钦定成不能选的了,就有$F_i=0$。
答案就是$\sum{F_p}$。单遍DP的复杂度是线性的,所以总复杂度$O(n^2)$。
存在至少一个点没有被选中时的EGF
---
随便选一个点$rt$做根。可以发现,此时最多只会有一个被“上下颠倒”了的子树。所以可以想到换根DP。
首先使用上一节的方法,求出以$rt$为根时的所有$F_i$。然后考虑$i$号点子树之内答案的EGF,把它记作$G_i(x)$。要不然是从每个子树选一个$G$组合起来,要不然就是单选一个$F_i$。所以转移是
$$G_i(x)=\frac{F_i}{siz_i!}x^{siz_i}+\prod_{j\textrm{是}i\textrm{的儿子}}G_j(x)$$
这里的卷积暴力实现即可。复杂度分析和树上背包是一模一样的,每一对点只会在它们的$LCA$处对复杂度产生贡献。所以是$O(n^2)$。
然后是换根的部分。我们通过一个DFS枚举每个点$p$,强制这个“上下颠倒”了的子树的根是$p$的父亲。**此时不是$p$后代的点必须全部被选中**。假设$p$子树之外的点全部选中时的方案数是$Out_p$,对整棵树EGF的贡献就是
$$\frac{Out_p}{(siz_{rt}-siz_p)!}x^{siz_{rt}-siz_p}(G_p(x)-\frac{F_p}{siz_p!}x^{siz_p})$$
从$G_p(x)$中扣除最后一项,是因为这里强制至少一个点不被选中了。容易发现每个点计算这个贡献的复杂度是$O(n)$的,所以总复杂度还是$O(n^2)$。
考虑$Out_p$从$p$转移到$p$的一个儿子$i$的过程,可以发现计算过程和$F_p$几乎是一模一样的:
$$Out_i=\frac{(siz_{rt}-siz_i+1)!}{(siz_{rt}-siz_p)!}Out_p\prod_{j\textrm{是}p\textrm{的儿子},j\ne i}\frac{F_j}{siz_j!}$$
这个暴力乘起来即可,复杂度是$O(n^2)$的。和$F$一样需要特判$p$点被钦定成不能选了的情况。
----
代码如下:
```cpp
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const ll mod=1e9+9;
int n,m,_,dfn[2010],low[2010],tp,id[2010],cnt;
int siz[2010],Rt,vis[2010],sz[2010],sta[2010],ban[2010];
ll fac[2010],ifc[2010],f[2010],g[2010][2010],h[2010],ans[2010],_ans[2010],F[2010],o[2010];
basic_string<int> vv[2010],v[2010];
ll po(ll a,ll b=mod-2){ll r=1;for(;b;b/=2,a=a*a%mod)if(b&1)r=r*a%mod;return r;}
void tarjan(int p,int fa){
dfn[p]=low[p]=++_;sta[++tp]=p;
for(int i:vv[p])if(!dfn[i])
tarjan(i,p),low[p]=min(low[p],low[i]);
else if(i!=fa)low[p]=min(low[p],dfn[i]);
if(low[p]==dfn[p]){
++cnt;int tot=0;
while(tp){
id[sta[tp]]=cnt;
tot++;
if(tot>1)ban[cnt]=1;
if(sta[tp--]==p)break;
}
}
}
void dfs(int p,int fa=0){
f[p]=siz[p]=1;vis[p]=1;
for(int i:v[p])if(i!=fa){
dfs(i,p);siz[p]+=siz[i];
(f[p]*=f[i]*ifc[siz[i]]%mod)%=mod;
}
g[p][0]=1;int sz=1;
for(int i:v[p])if(i!=fa){
memset(h,0,sizeof h);
for(int j=0;j<sz+siz[i];j++)
for(int k=0;k<=siz[i] && k<=j;k++)
(h[j]+=g[i][k]*g[p][j-k])%=mod;
sz+=siz[i];
for(int j=0;j<sz;j++)g[p][j]=h[j];
}
(f[p]*=fac[siz[p]-1])%=mod;
if(ban[p])f[p]=0;
g[p][siz[p]]=f[p]*ifc[siz[p]]%mod;
}
void dfs2(int p,int fa=0){
F[p]=sz[p]=1;
for(int i:v[p])if(i!=fa){
dfs2(i,p);sz[p]+=sz[i];
(F[p]*=F[i]*ifc[sz[i]]%mod)%=mod;
}
(F[p]*=fac[sz[p]-1])%=mod;
if(ban[p])F[p]=0;
}
void dfs1(int p,int fa=0){
ll op=o[p]*ifc[siz[Rt]-siz[p]]%mod;
for(int i=0;i<siz[p];i++)
(ans[i+siz[Rt]-siz[p]]+=g[p][i]*op)%=mod;
dfs2(p);
(ans[siz[Rt]]+=F[p]*ifc[siz[Rt]])%=mod;
for(int i:v[p])if(i!=fa){
o[i]=op;
for(int j:v[p])if(j!=fa && i!=j)(o[i]*=f[j]*ifc[siz[j]]%mod)%=mod;
(o[i]*=fac[siz[Rt]-siz[i]-1])%=mod;
if(ban[p])o[i]=0;
dfs1(i,p);
}
}
int main(){
scanf("%d%d",&n,&m);
ifc[0]=fac[0]=1;
for(int i=1;i<=n;i++)ifc[i]=po(fac[i]=fac[i-1]*i%mod);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
vv[x]+=y,vv[y]+=x;
}
for(int i=1;i<=n;i++)if(!dfn[i])tp=0,tarjan(i,0);
for(int i=1;i<=n;i++)for(int j:vv[i])if(id[j]!=id[i] && !~v[id[j]].find(id[i]))
v[id[j]]+=id[i],v[id[i]]+=id[j];
_=1;_ans[0]=1;
for(int i=1;i<=cnt;i++)if(!vis[i]){
memset(ans,0,sizeof ans);
dfs(i);Rt=i;o[Rt]=1;
dfs1(Rt);
memset(h,0,sizeof h);
for(int i=0;i<_+siz[Rt];i++)
for(int j=0;j<=siz[Rt] && j<=i;j++)
(h[i]+=ans[j]*_ans[i-j])%=mod;
_+=siz[Rt];
memcpy(_ans,h,sizeof _ans);
}
for(int i=0;i<=n;i++)
printf("%lld\n",(_ans[i]*fac[i]%mod+mod)%mod);
return 0;
}
```