洛谷P5333/bzoj5528/loj3102 [JSOI2019]神经网络 树形DP+生成函数

题目分析

链划分

显然,一条欧拉路是在一棵树上走一条链,然后跳到另一棵树上走一条链,再跳……

可以利用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)=0aini(G(a1,a2,...,ak)i=0kCniai)

则有:

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)=0aini(F(a1,a2,...,ak)i=0k(1)niaiCniai)

生成函数

依旧不考虑环而考虑链。

本题中的反演关系,可以看做,若有可以有相邻同色球,就将它们缩成一个球,于是就变成了一个没有相邻同色球的序列。可以有相邻同色的函数,定义为 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=1nCn1i1(1)nii!xi

n ! n! n!是因为同种颜色的球依然都是不同球, C n − 1 i − 1 C_{n-1}^{i-1} Cn1i1表示将 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=1nfii!j=1iCi1j1(1)ijj!xj

只剩最后一步,就是扩展到环上了。

那么先卷好前 m − 1 m-1 m1种颜色。对于第 m m m种颜色,我钦定这种颜色中的一条确定的链为拆环为链后,链的开头。而链的结尾,一定是前 m − 1 m-1 m1种颜色中的一种。

考虑合并前 m − 1 m-1 m1种颜色,已经钦定好的一条链,和当前颜色链。设前 m − 1 m-1 m1种颜色构造的链长为 A A A,那么合并方案数为:

( A − 1 + a m − 1 ) ! ( A − 1 ) ! ( a m − 1 ) ! \frac{(A-1+a_m-1)!}{(A-1)!(a_m-1)!} (A1)!(am1)!(A1+am1)!

所以,将原来那个前 m − 1 m-1 m1种颜色的生成函数, i i i次项系数乘以 i ! i! i!,再除以 ( i − 1 ) ! (i-1)! (i1)!(即乘以 i i i)后,赋给第 i − 1 i-1 i1次项。

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=1nfi(i1)!j=1iCi1j1(1)ij(j1)!xj1

卷起来,再计算答案即可。

代码

#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;
}

你可能感兴趣的:(数学,动态规划,生成函数,容斥,树形DP,DP)