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\) 次。不难发现这是一个排列计数的模型,此时的贡献为:
然后子树内的贡献显然是独立可乘的,于是我们只需要考虑每棵子树的答案,考虑子树 \(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^{z\sum 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 ;
}
原文地址:https://www.cnblogs.com/Soulist/p/13828828.html
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Qt音视频开发15-mpv事件订阅
- MySQL案例:8.0统计信息不准确?
- Chaos Mesh® X GitHub Actions —— 把混沌工程集成到你的 CI 中
- 链表常见操作总结及C++实现
- 跳表原理及C++实现
- MySQL 8.0 新特性:WriteSet 复制
- leetcode: explore-array-30 有效的数独
- leetcode: explore-array-29 两数之和
- leetcode: explore-array-28 移动零
- leetcode: explore-array-27 加一
- leetcode: explore-array-26 列表取交集 II
- leetcode 找出唯一一个只出现一次的数字
- leetcode: 找出不重复的列表
- Python “原地”旋转数组
- Python backoff 实现轮询