• 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) 条边的方案数。(允许重复经过点/边)

    (nle 4000,kle 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!prodigg(sum_j frac{F_v(x)^jz^j}{j!}igg)[z^l] ]

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

    此时,所求即为:

    [sum_l l! imes 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 ;
    }
    
  • 相关阅读:
    二阶段任务分配
    二阶段12.2
    针对提出的意见的改进
    一阶段spring(小呆呆)团队评分
    搜狗输入法使用感受
    省呱呱典型用户和用户场景
    省呱呱意见评论
    11/21
    11/20小组计划
    11/19小组计划
  • 原文地址:https://www.cnblogs.com/Soulist/p/13828828.html
Copyright © 2020-2023  润新知