显然,一条欧拉路是在一棵树上走一条链,然后跳到另一棵树上走一条链,再跳……
可以利用DP求出,每棵树有多少种链划分方式(注意一条链“从这头走到那头”和“从那头走到这头”算两种不同的划分方式)
DP方法:设 f ( x , i , 0 / 1 / 2 ) f(x,i,0/1/2) f(x,i,0/1/2)表示以 x x x为根的子树, x x x所在的链往子树里伸入的有0/1/2根,一共划分为 i i i条链的方案数。然后用那种经典的,利用当前 s i z e size size值精细实现的,均摊 O ( n 2 ) O(n^2) O(n2)的DP即可算出。
复杂度证明->here
假设我们已经钦定了每一棵树被划分为多少条链,那么问题转化为,有 m m m种不同颜色的珠子,第 i i i种颜色的有 n i n_i ni种,现在要将它们串成个环,不能有相邻两个珠子颜色相同,求方案数。
环的情况还不太好做,串成个链呢?
这就是CSAcademy的Distinct Neighbors了,有一种经典的DP方法可以解决这个问题:
设 f ( i , j ) f(i,j) f(i,j)表示考虑了前 i i i个颜色,相邻的颜色相同的珠子有 j j j对。对于第 i + 1 i+1 i+1种珠子,考虑将它们插入几个空隙中(即可算出相邻的颜色都为 i + 1 i+1 i+1的珠子,即 j j j的增加量),这些空隙里有多少个是两个相同颜色珠子之间的空隙(即可算出 j j j的减少量),再用组合数选空隙即可,注意本题不同于CSAcademy的那道,同一个颜色的珠子,依旧都是不同的珠子。
这种DP是 O ( n 3 ) O(n^3) O(n3)的,虽然很优美,但复杂度既不够优秀,也无法处理每种颜色的珠子数量还没被钦定的情况。
那么如何将她扩展到可以解决这道题呢?
没法扩展的(至少我不会)。
那我为什么还要介绍这种做法呢?
逗你玩,嘿嘿。
(好吧其实是因为她就是很优美,有介绍一下的必要)
我管这个方法叫做高维容斥。 ——boshi
引入一个“高维容斥(高维反演)”的概念。
若定义
F ( n 1 , n 2 , . . . , n k ) = ∑ 0 ≤ a i ≤ n i ( G ( a 1 , a 2 , . . . , a k ) ∏ i = 0 k C n i a i ) F(n_1,n_2,...,n_k)=\sum_{0 \leq a_i \leq n_i}(G(a_1,a_2,...,a_k) \prod_{i=0}^k C_{n_i}^{a_i}) F(n1,n2,...,nk)=0≤ai≤ni∑(G(a1,a2,...,ak)i=0∏kCniai)
则有:
G ( n 1 , n 2 , . . . , n k ) = ∑ 0 ≤ a i ≤ n i ( F ( a 1 , a 2 , . . . , a k ) ∏ i = 0 k ( − 1 ) n i − a i C n i a i ) G(n_1,n_2,...,n_k)=\sum_{0 \leq a_i \leq n_i}(F(a_1,a_2,...,a_k) \prod_{i=0}^k (-1)^{n_i-a_i}C_{n_i}^{a_i}) G(n1,n2,...,nk)=0≤ai≤ni∑(F(a1,a2,...,ak)i=0∏k(−1)ni−aiCniai)
依旧不考虑环而考虑链。
本题中的反演关系,可以看做,若有可以有相邻同色球,就将它们缩成一个球,于是就变成了一个没有相邻同色球的序列。可以有相邻同色的函数,定义为 F F F,不可以的,定义为 G G G,就满足一个类似于上式(不完全相同)的关系。
F F F也很好算,就是 ( n 1 + n 2 + . . . + n k ) ! n 1 ! n 2 ! . . . n k ! \frac{(n_1+n_2+...+n_k)!}{n_1!n_2!...n_k!} n1!n2!...nk!(n1+n2+...+nk)!。
利用高维容斥的概念,构造出,假如 n i n_i ni已经被钦定的生成函数:
n ! ∑ i = 1 n C n − 1 i − 1 ( − 1 ) n − i x i i ! n! \sum_{i=1}^n C_{n-1}^{i-1} (-1)^{n-i} \frac{x^i}{i!} n!i=1∑nCn−1i−1(−1)n−ii!xi
( n ! n! n!是因为同种颜色的球依然都是不同球, C n − 1 i − 1 C_{n-1}^{i-1} Cn−1i−1表示将 n n n个球分成 i i i段的方案数。)
只要将每种颜色的生成函数都卷起来,然后将 i i i次项乘以 i ! i! i!,再都加起来即可。
接下来,考虑没有钦定,那么只要将每一种划分带来的情况加起来再卷即可(设 f i f_i fi表示这棵树划分为 i i i条链的方案数)。
∑ i = 1 n f i i ! ∑ j = 1 i C i − 1 j − 1 ( − 1 ) i − j x j j ! \sum_{i=1}^n f_i i! \sum_{j=1}^i C_{i-1}^{j-1}(-1)^{i-j} \frac{x^j}{j!} i=1∑nfii!j=1∑iCi−1j−1(−1)i−jj!xj
只剩最后一步,就是扩展到环上了。
那么先卷好前 m − 1 m-1 m−1种颜色。对于第 m m m种颜色,我钦定这种颜色中的一条确定的链为拆环为链后,链的开头。而链的结尾,一定是前 m − 1 m-1 m−1种颜色中的一种。
考虑合并前 m − 1 m-1 m−1种颜色,已经钦定好的一条链,和当前颜色链。设前 m − 1 m-1 m−1种颜色构造的链长为 A A A,那么合并方案数为:
( A − 1 + a m − 1 ) ! ( A − 1 ) ! ( a m − 1 ) ! \frac{(A-1+a_m-1)!}{(A-1)!(a_m-1)!} (A−1)!(am−1)!(A−1+am−1)!
所以,将原来那个前 m − 1 m-1 m−1种颜色的生成函数, i i i次项系数乘以 i ! i! i!,再除以 ( i − 1 ) ! (i-1)! (i−1)!(即乘以 i i i)后,赋给第 i − 1 i-1 i−1次项。
第 m m m种颜色的生成函数,改为:
∑ i = 1 n f i ( i − 1 ) ! ∑ j = 1 i C i − 1 j − 1 ( − 1 ) i − j x j − 1 ( j − 1 ) ! \sum_{i=1}^n f_i (i-1)! \sum_{j=1}^i C_{i-1}^{j-1}(-1)^{i-j} \frac{x^{j-1}}{(j-1)!} i=1∑nfi(i−1)!j=1∑iCi−1j−1(−1)i−j(j−1)!xj−1
卷起来,再计算答案即可。
#include
using namespace std;
#define RI register int
const int mod=998244353,N=5005;
int m,tot,n,nown;
int h[N],ne[N<<1],to[N<<1],sz[N],f[N][N][3],tmp[N][3];
int fac[N],inv[N],ifac[N],A[N],B[N],C[N];
int qm(int x) {return x>=mod?x-mod:x;}
int qpow(int x,int y) {
int re=1;
for(;y;y>>=1,x=1LL*x*x%mod) if(y&1) re=1LL*re*x%mod;
return re;
}
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void dfs(int x,int las) {
sz[x]=1,f[x][1][0]=1,f[x][1][1]=f[x][1][2]=0;
for(RI i=h[x];i;i=ne[i]) {
int y=to[i];if(y==las) continue;
dfs(y,x);
for(RI j=1;j<=sz[x];++j)
tmp[j][0]=f[x][j][0],tmp[j][1]=f[x][j][1],tmp[j][2]=f[x][j][2];
for(RI j=1;j<=sz[x]+sz[y];++j) f[x][j][0]=f[x][j][1]=f[x][j][2]=0;
for(RI j=1;j<=sz[x];++j)
for(RI k=1;k<=sz[y];++k) {
int sumf_y=qm(qm(f[y][k][0]+2LL*f[y][k][1]%mod)+f[y][k][2]);
for(RI t=0;t<=2;++t)
f[x][j+k][t]=qm(f[x][j+k][t]+1LL*tmp[j][t]*sumf_y%mod);
f[x][j+k-1][1]=qm(f[x][j+k-1][1]+
1LL*tmp[j][0]*qm(f[y][k][0]+f[y][k][1])%mod);
f[x][j+k-1][2]=qm(f[x][j+k-1][2]+
1LL*tmp[j][1]*qm(f[y][k][0]+f[y][k][1])%mod);
}
sz[x]+=sz[y];
}
for(RI i=1;i<=sz[x];++i) f[x][i][2]=qm(f[x][i][2]+f[x][i][2]);
}
int comb(int d,int u) {
if(u==0) return 1;
if(d<u) return 0;
return 1LL*fac[d]*ifac[u]%mod*ifac[d-u]%mod;
}
void getA(int o) {
for(RI i=0;i<=n;++i) A[i]=0;
for(RI i=1;i<=n;++i) {
int sumf=qm(qm(f[1][i][0]+f[1][i][1])+f[1][i][2])%mod;
if(o) sumf=1LL*sumf*fac[i-1]%mod;
else sumf=1LL*sumf*fac[i]%mod;
for(RI j=1;j<=i;++j)
if((i-j)&1) A[j]=qm(A[j]-1LL*sumf*comb(i-1,j-1)%mod+mod);
else A[j]=qm(A[j]+1LL*sumf*comb(i-1,j-1)%mod);
}
for(RI i=1;i<=n;++i) A[i]=1LL*A[i]*ifac[i]%mod;
}
void convolution(int n1,int n2) {
for(RI i=0;i<=n1+n2;++i) C[i]=0;
for(RI i=0;i<=n1;++i)
for(RI j=0;j<=n2;++j) C[i+j]=qm(C[i+j]+1LL*A[i]*B[j]%mod);
for(RI i=0;i<=n1+n2;++i) B[i]=C[i];
}
void getans() {
for(RI i=0;i<=n-1;++i) A[i]=1LL*A[i+1]*(i+1)%mod;
for(RI i=0;i<=nown-1;++i) B[i]=1LL*B[i+1]*(i+1)%mod;
convolution(n-1,nown-1);
int ans=0;
for(RI i=0;i<=n+nown-2;++i) ans=qm(ans+1LL*fac[i]*B[i]%mod);
printf("%d\n",ans);
}
int main()
{
int x,y;
fac[0]=1;for(RI i=1;i<=5000;++i) fac[i]=1LL*fac[i-1]*i%mod;
ifac[0]=1,inv[0]=inv[1]=1;
for(RI i=2;i<=5000;++i) inv[i]=1LL*(mod-mod/i)*inv[mod%i]%mod;
for(RI i=1;i<=5000;++i) ifac[i]=1LL*ifac[i-1]*inv[i]%mod;
scanf("%d",&m),B[0]=1;
for(RI i=1;i<=m;++i) {
scanf("%d",&n);
if(m==1) {
if(n==1) puts("1");
else puts("0");
return 0;
}
for(RI j=1;j<n;++j) scanf("%d%d",&x,&y),add(x,y),add(y,x);
dfs(1,0);
for(RI j=1;j<=n;++j) f[1][j][1]=qm(f[1][j][1]+f[1][j][1]);
if(i==m) getA(1),getans();
else getA(0),convolution(n,nown),nown+=n;
tot=0;for(RI j=1;j<=n;++j) h[j]=0;
}
return 0;
}