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) 次。不难发现这是一个排列计数的模型,此时的贡献为:
然后子树内的贡献显然是独立可乘的,于是我们只需要考虑每棵子树的答案,考虑子树 (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),不难发现答案即:
显然的是,后者是 (e^{F_v(x)z}),同时乘积可以换成求和,即 (e^{zsum F_v(x)}),设 (G(x)=sum F_v(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 ;
}