CF997D

时间:2020-10-16
本文章向大家介绍CF997D,主要包括CF997D使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

CF997D Cycles in product [* easy]

给定两棵树 \(T_1,T_2\),生成图 \(G\),每个点为点对 \((x,y)\), 满足对于 \(T_1\),任意 \(u_1,u_2\) 存在边,则 \(\forall v, (u_1,v),(u_2,v)\) 存在一条边,同理于 \(T_2\)

求从一个点出发走回自己,经过了恰好 \(k\) 条边的方案数。(允许重复经过点/边)

\(n\le 4000,k\le 75\)

Solution

可以假设有两个维度,那么两个维度的移动是独立的,同时由于每个点对都需要算一次贡献,我们可以预先处理一下 \(T_1\) 上走回自己的方案数以及 \(T_2\) 走回自己的方案数,然后做一下 EGF 的卷积就知道答案了。

考虑给定一棵树,我们怎么计算从 \(x\) 出发走到自己经过了 \(k\) 条边的方案数。

显然可以 \(\mathcal O(n^2k)\)

能不能更优呢?

可以这样考虑,先假设我们只考虑 \(x\) 的子树内走回自己的方案数,我们假设这个 dp 数组是 \(f\)

考虑怎么确定 \(u\) 的 dp 值,假设 \(u\) 有很多个出边,那么我们可以考虑依次确定每棵子树被走了多少次,假设分别是 \(c_1,c_2...c_k\) 次。不难发现这是一个排列计数的模型,此时的贡献为:

\[\frac{(\sum c_i)!}{\prod c_i!} \]

然后子树内的贡献显然是独立可乘的,于是我们只需要考虑每棵子树的答案,考虑子树 \(v\) 的答案。

考虑子树 \(v\) 走了 \(j\) 条边,且经过了 \(c\) 次的方案数,单独拿一个 dp 来算,不难发现本质在做一个卷积状物,事实上,假设我们列出 \(v\) 子树的 dp 值的生成函数 \(F_v(x)\),答案就是 \((F_v(x)x^2)^c\)(注意我们每次走进去至少要额外花费两条边,这样可以直接令 \(F_v(x)\leftarrow F_v(x)x^2\)

现在考虑统计答案,增添一个维度 \(z\),不难发现答案即:

\[\sum_l l!\prod\bigg(\sum_j \frac{F_v(x)^jz^j}{j!}\bigg)[z^l] \]

显然的是,后者是 \(e^{F_v(x)z}\),同时乘积可以换成求和,即 \(e^{z\sum F_v(x)}\),设 \(G(x)=\sum F_v(x)\)

此时,所求即为:

\[\sum_l l!\times \frac{G(x)^l}{l!} \]

即:

\[\sum G(x)^l=\frac{1}{1-G(x)} \]

暴力求逆是 \(\mathcal O(k^2)\) 的,同时我们导出了一个优美的结论:

对于 \(u\) 而言,仅考虑其子树内情况下,以 \(u\) 出发回到 \(u\) 的答案多项式,即其各子树的多项式乘以 \(x^2\) 后的和 \(G(x)\) 的若干次幂之和,即 \(\frac{1}{1-G(x)}\)

于是我们显然可以 \(\mathcal O(nk^2)\) 的推出根节点的答案。

然后我们可以 \(\mathcal O(nk^2)\) 的进行换根,操作手段为直接维护这个点处其他所有子树的多项式的和,换根时减去多项式,然后再次求逆即可。复杂度 \(\mathcal O(nk^2)\)

\(Code:\)

#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define Rep( i, s, t ) for( register int i = (s); i < (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
#define vi vector<int>
#define int long long
#define pb push_back
int gi() {
	char cc = getchar() ; int cn = 0, flus = 1 ;
	while( cc < '0' || cc > '9' ) {  if( cc == '-' ) flus = - flus ; cc = getchar() ; }
	while( cc >= '0' && cc <= '9' )  cn = cn * 10 + cc - '0', cc = getchar() ;
	return cn * flus ;
}
const int N = 4000 + 5 ; 
const int K = 80 + 5 ; 
const int P = 998244353 ; 
int n1, n2, L, ans[2][N], fac[N], inv[N], F[N][K], G[N][K], H[N][K], D[N][K] ; 
vi Go[N] ; 
int fpow(int x, int k) {
	int a = 1, base = x ;
	while(k) {
		if(k & 1) a = 1ll * a * base % P ;
		base = 1ll * base * base % P, k >>= 1 ;
	} return a ;
}
void add(int x, int y) { Go[x].pb(y), Go[y].pb(x) ; }
int Fp[N], A[N], B[N] ; 
void Inv(int *a) {
	rep( j, 0, L ) Fp[j] = 0 ; Fp[0] = fpow(a[0], P - 2) ; 
	rep( j, 1, L ) Rep( k, 0, j ) Fp[j] = ( Fp[j] - Fp[k] * A[j - k] % P + P ) % P ;  
	rep( j, 0, L ) a[j] = Fp[j] ; 
}
void Get(int *a) {
	rep( j, 0, L ) A[j] = 0 ; 
	rep( j, 2, L ) A[j] = (P - a[j - 2]) % P ; 
	A[0] = 1, Inv(A) ;
	rep( j, 0, L ) a[j] = A[j] ; 
}
void dfs(int x, int fa, int type) {
	for(int v : Go[x]) {
		if(v == fa) continue ; 
		dfs(v, x, type) ; rep( j, 0, L ) G[x][j] = (G[x][j] + F[v][j]) % P ; 
	} 
	rep( j, 0, L ) F[x][j] = G[x][j] ; Get(F[x]) ; 
}
void Dfs(int x, int fa, int type) {
	if(x != fa) {
		rep( j, 0, L ) B[j] = ( H[fa][j] - F[x][j] + P ) % P ; Get(B) ; 
		rep( j, 0, L ) D[x][j] = H[x][j] = (G[x][j] + B[j]) % P ; Get(D[x]) ; 
		rep( j, 0, L ) ans[type][j] = (ans[type][j] + D[x][j]) % P ; 
	}
	for(int v : Go[x]) if(v ^ fa) Dfs(v, x, type) ; 
}
int C(int x, int y) { return fac[x] * inv[y] % P * inv[x - y] % P ; }
signed main()
{
	n1 = gi(), n2 = gi(), L = gi() ; int x, y ; 
	rep( i, 2, n1 ) x = gi(), y = gi(), add(x, y) ; 
	dfs(1, 1, 0) ; 
	rep( j, 0, L ) H[1][j] = G[1][j], D[1][j] = F[1][j] ; 
	rep( j, 0, L ) ans[0][j] = (ans[0][j] + F[1][j]) % P ; 
	Dfs(1, 1, 0) ; 
	memset( H, 0, sizeof(H) ), memset( G, 0, sizeof(G) ),
	memset( D, 0, sizeof(D) ), memset( F, 0, sizeof(F) ) ;
	rep( i, 1, n1 ) Go[i].clear() ; 
	rep( i, 2, n2 ) x = gi(), y = gi(), add(x, y) ; 
	dfs(1, 1, 1) ; 
	rep( j, 0, L ) H[1][j] = G[1][j], D[1][j] = F[1][j] ; 
	rep( j, 0, L ) ans[1][j] = (ans[1][j] + F[1][j]) % P ; 
	Dfs(1, 1, 1) ; fac[0] = inv[0] = 1 ; 
	rep( i, 1, L ) fac[i] = fac[i - 1] * i % P, inv[i] = fpow(fac[i], P - 2) ;
	int Ans = 0 ;
	rep( i, 0, L ) Ans = (Ans + ans[0][i] * ans[1][L - i] % P * C(L, i) ) % P ; 
	cout << Ans << endl ; 
	return 0 ;
}

$flag 上一页 下一页