学习笔记:生成函数II(集合分拆、置换、整数分拆、它们的递推公式、生成函数 和快速计算)
形式幂级数的更多运算#
形式幂级数与幂级数的比较#
- 形式幂级数本质是序列(
无意义),幂级数本质是极限。 - 形式幂级数通过带入
还原成幂级数。 - 假设系数在
上,可以证明形式幂级数与具有正收敛半径的幂级数在 '通常' 的所有运算上服从同样规律(加减乘除求导积分……)。
形式幂级数的更多运算#
假设
求导#
定义
对于形式幂级数而言,
求导是序列上的一个变换,即
积分#
定义
即
推论:如果
复合#
假设
则
记做
如果
的常数项不为 。 那么
。 则
本身就是一个极限的形式,涉及到收不收敛的问题,这是在形式幂级数中不愿看到的。 因此在定义复合时,一般令
。 再考虑
。 如果
,那么 ,所以 的定义不需要考虑收敛。
。 。 。- 设形式幂级数
满足 ,则可定义 和 。(或 可定义 )。 。
推论:
假设
满足
即证
左边:
右边:
左边等于右边。
计算 #
(满足
计算 #
(满足
构造牛顿迭代
有递推式
例题#
三轮#
题意:
对质数
考虑其生成函数
所以
由于
将积化为和,两边取对数。
第二类斯特林数#
第二类斯特林数(斯特林子集数)
递推关系#
我们插入一个新元素时,有两种方案:
- 将新元素放入一个现有的非空子集,有
种方案。 - 将新元素单独放入一个子集,有
种方案;
边界是
通项公式#
不妨先认为
则
重要公式#
记下降阶乘幂
则可以利用下面的恒等式将普通幂转化为下降幂:
考虑各式组合意义。
个不同的球放入 个不同的盒子。 个盒子中选出 个。 个不同的球放入 个相同的盒子且都不为空。 将选出的 个盒子排列。
正确性显然。
指数生成函数#
不妨将盒子染成
对于第一种颜色,
全部的合法方案为
最后将染色去除,得到
第二类斯特林数·列#
利用公式
其中
如果
考虑给每项除以一个公因式
最后再乘上
EGF 求得的系数为
。
第二类斯特林数·行#
令
于是
第一类斯特林数#
有
置换向原位置连边,形成若干个环,即轮换。
对于置换
,有两个轮换:
递推关系#
边界:
。
-
自己向自己连边,单独成环。
-
已经有了
个环,在 个空隙里选一个插入。
重要公式#
上升幂转普通幂#
假设
联立
得到
发现
下降幂转普通幂#
用
把左式的负号移至右式,即证。
指数生成函数#
等效于
先将盒子编号,对于每个盒子,有生成函数
有
第一类斯特林数·列#
第一类斯特林数·行#
展开
显然可以分治 ntt
考虑倍增。
令
问题转化为已知多项式
第二个求和式可以写成
则
时间复杂度
有符号的第一类斯特林数#
定义:
重要公式
指数生成函数
两类斯特林数的关系及斯特林反演#
证明:
对于公式
对于 有符号 第一类斯特林数:
我们有
则在
即
斯特林反演
证明:
整数拆分#
的 无序拆分#
递推关系#
假设第
分两部分考虑。
- 第
个盒子有且仅有新加入的一个球,方案为 。 - 第
个盒子不止一个球,把黄色一列去掉,方案为 。
常生成函数#
考虑上图中有多少有
因此其常生成函数为
记
由于至少要有一列为
- 快速求
的前 项:对后式先取 再 ,详见 形式幂级数的更多运算-例题-三轮。
的无序拆分#
可知
常生成函数#
延续上板的思考方向,行的个数没有限制,所以只要一些长度为
那么
递推关系#
证明繁琐,当结论记。
产生贡献的项只有
主要运用于模数不友好的情况。
生成函数模型#
分配问题总结#
p(n + k, k):把每行之前都加一个球,则
个盒子都不为空
分配问题(加强版1)#
把
设
则
分配问题(加强版2)#
把
设
即对加强版
无标号/无标号:
考虑大小为
对于盒子的第一种形态
以此类推,其常生成函数为
所以无标号/无标号的常生成函数为
例题#
CF961G Partitions#
题意:
给出
定义一个集合
定义一个划分的权值为
求将
感性理解,每个数的贡献是相同的,所以答案一定是
枚举当前元素所在集合大小。
单独计算后面一个求和式,把
则
时间复杂度
还有一种更为精巧的解法:
考虑
在一个大小为
分两部分讨论
-
自己使自己的产生的贡献,显然每一种划分产生一次,共
次。 -
其他元素使自己的产生的贡献,把其余
个球划分好 个集合,对于每种划分,当前元素可以任选一个集合放进去,因此 个物品都会有一次贡献,共 。
综上
CF960G Bandit Blues#
题意:给定
首先,排列里最大值一定同时是前缀最大和后缀最大,如果存在
令
不妨从大到小一个一个填。
- 填到序列的最前面,一定是前缀最大,有
种情况。 - 由于所有数都比他大,填到任意一个数后面,有
种情况。
所以
枚举最大值左边的元素个数。
则答案为
可以直接求出两列斯特林数,但形式仍不够优美。
考虑式子的组合意义:选出
也就是
于是答案化简为
第一列斯特林数没有实用的通项公式,随便求出一行或一列即可。
附录:模板#
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int N = 5e5 + 5, P = 998244353; // g = 3
ll gg[30], ig[30], fac[N], ifac[N], inv[N];
ll qpow(ll a, ll b) {
ll ret = 1;
while(b) {
if(b & 1) ret = ret * a % P;
b >>= 1;
a = a * a % P;
}
return ret;
}
void init() {
gg[0] = ig[0] = 1;
for(int i = 1; i < 30; ++ i) gg[i] = qpow(3, (P - 1) / (1 << i));
for(int i = 1; i < 30; ++ i) ig[i] = qpow(gg[i], P - 2);
fac[0] = 1;
for(int i = 1; i < N; ++ i) {
inv[i] = (i == 1) ? 1 : -(P / i) * inv[P % i] % P;
fac[i] = fac[i - 1] * i % P;
}
ifac[N - 1] = qpow(fac[N - 1], P - 2);
for(int i = N - 1; i >= 1; -- i) {
ifac[i - 1] = ifac[i] * i % P;
}
}
int rev[N];
void ntt(ll *a, int tot, int ty) {
for(int i = 0; i < tot; ++ i) {
if(i < rev[i]) {
swap(a[i], a[rev[i]]);
}
}
int t = 1;
for(int mid = 1; mid < tot; mid <<= 1, ++ t) {
ll g1 = gg[t];
if(ty == -1) {
g1 = ig[t];
}
for(int i = 0; i < tot; i += mid * 2) {
ll gk = 1;
for(int j = 0; j < mid; ++ j, gk = gk * g1 % P) {
ll x = a[i + j], y = a[i + j + mid];
a[i + j] = (x + gk * y) % P;
a[i + j + mid] = (x - gk * y) % P;
}
}
}
if(ty == -1) {
ll tmp = qpow(tot, P - 2);
for(int i = 0; i < tot; ++ i) {
a[i] = a[i] * tmp % P;
}
}
}
void polymul(ll *f, const ll *g, const ll *h, int n, int m) { // 项数,非次数
static ll a[N], b[N];
int tot = 1, bit = 0;
while(tot < n + m - 1) ++ bit, tot <<= 1;
for(int i = 0; i < tot; ++ i) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << bit - 1);
memcpy(a, g, n * 8), memset(a + n, 0, (tot - n) * 8);
memcpy(b, h, m * 8), memset(b + m, 0, (tot - m) * 8);
ntt(a, tot, 1), ntt(b, tot, 1);
for(int i = 0; i < tot; ++ i) a[i] = a[i] * b[i] % P;
ntt(a, tot, -1);
memcpy(f, a, tot * 8);
}
void polyinv(ll *f, const ll *h, int n){
static ll d[N], g[N];
int V = 1; while(V < n + n - 1) V <<= 1;
memcpy(d, h, n * 8), memset(d + n, 0, (V - n) * 8);
memset(f, 0, V * 8), memset(g, 0, V * 8);
f[0] = qpow(h[0], P - 2);
for(int w = 2; w / 2 < n; w <<= 1){
memcpy(g, d, w * 8);
for(int i = 0; i < w * 2; ++i) rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? w : 0);
ntt(f, w << 1, 1), ntt(g, w << 1, 1);
for(int i = 0; i < w * 2; ++i) f[i] = (2 - f[i] * g[i]) % P * f[i] % P;
ntt(f, w << 1, -1);
memset(f + w, 0, w * 8);
}
memset(f + n, 0, (V - n) * 8);
}
void polysqrt(ll *f, const ll *h, int n){
static ll d[N], g[N], f_inv[N];
int V = 1; while(V < n + n - 1) V <<= 1;
memcpy(d, h, n * 8), memset(d + n, 0, (V - n) * 8);
memset(f, 0, V * 8), memset(g, 0, V * 8), memset(f_inv, 0, V * 8);
f[0] = 1;
constexpr int i2 = 499122177;
for(int w = 2; w / 2 < n; w <<= 1){
memcpy(g, d, w * 8);
for(int i = 0; i < w * 2; ++i) rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? w : 0);
polyinv(f_inv, f, w);
ntt(f, w << 1, 1), ntt(g, w << 1, 1), ntt(f_inv, w << 1, 1);
for(int i = 0; i < w * 2; ++i) f[i] = (f[i] + f_inv[i] * g[i]) % P * i2 % P;
ntt(f, w << 1, -1);
memset(f + w, 0, w * 8);
}
memset(f + n, 0, (V - n) * 8);
}
void polyder(ll *f, const ll *h, int n) { // 项数,非次数
for(int i = 0; i <= n - 1; ++ i) {
f[i] = (i + 1) * h[i + 1] % P;
}
f[n - 1] = 0;
}
void polyint(ll *f, const ll *h, int n) { // 项数,非次数
for(int i = n - 1; i >= 1; -- i) {
f[i] = h[i - 1] * inv[i] % P;
}
f[0] = 0;
}
void polyln(ll *f, const ll *h, int n) { // h[0] = 1
static ll a[N], b[N];
polyinv(a, h, n);
polyder(b, h, n);
polymul(a, a, b, n, n);
polyint(f, a, n);
}
void polyexp(ll *f, const ll *h, int n) { // h[0] = 0
static ll d[N], g[N], f_ln[N];
int V = 1; while(V < n + n - 1) V <<= 1;
memcpy(d, h, n * 8), memset(d + n, 0, (V - n) * 8);
memset(f, 0, V * 8), memset(g, 0, V * 8), memset(f_ln, 0, V * 8);
f[0] = 1; //ln(h[0])
for(int w = 2; w / 2 < n; w <<= 1){
memcpy(g, d, w * 8);
for(int i = 0; i < w * 2; ++i) rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? w : 0);
polyln(f_ln, f, w);
ntt(f, w << 1, 1), ntt(g, w << 1, 1), ntt(f_ln, w << 1, 1);
for(int i = 0; i < w * 2; ++i) f[i] = (1 - f_ln[i] + g[i]) % P * f[i] % P;
ntt(f, w << 1, -1);
memset(f + w, 0, w * 8);
}
memset(f + n, 0, (V - n) * 8);
}
void polypow(ll *f, ll *h, string K, int n) {
int cur = 0;
while(cur < n && h[cur] == 0) ++ cur;
ll k1 = 0, k2 = 0;
for(auto ch : K) {
if((k1 * 10 + ch - '0') * cur >= n || cur == n) {
for(int i = 0; i < n; ++ i) f[i] = 0;
return;
}
k1 = (k1 * 10 + ch - '0') % P;
k2 = (k2 * 10 + ch - '0') % (P - 1);
}
ll h_cur = h[cur], icur = qpow(h_cur, P - 2);
for(int i = cur; i < n; ++ i) {
f[i - cur] = h[i] * icur % P;
}
polyln(f, f, n - cur);
for(int i = 0; i < n - cur; ++ i) f[i] = f[i] * k1 % P;
polyexp(f, f, n - cur);
h_cur = qpow(h_cur, k2);
for(int i = n - 1; i >= k1 * cur; -- i) f[i] = f[i - k1 * cur] * h_cur % P;
for(int i = 0; i < k1 * cur; ++ i) f[i] = 0;
}
void Stirling2ndCol(ll *f, int n, int k) { //S2(0...n, k)
for(int i = 1; i <= n; ++ i) {
f[i] = ifac[i];
}
polypow(f, f, to_string(k), n + 1);
for(int i = 0; i <= n; ++ i) {
f[i] = f[i] * ifac[k] % P * fac[i] % P;
}
}
void Stirling2ndRow(ll *f, int n) { // S2(n, 0...n)
static ll g[N];
for(int i = 0; i <= n; ++ i) {
f[i] = (i & 1) ? -ifac[i] : ifac[i];
g[i] = qpow(i, n) * ifac[i] % P;
}
polymul(f, f, g, n + 1, n + 1);
}
void Stirling1stCol(ll *f, int n, int k) { //c(0...n, k)
for(int i = 2; i <= n; ++ i) {
f[i] = 0;
}
f[0] = 1, f[1] = -1;
polyln(f, f, n + 1);
for(int i = 0; i <= n; ++ i) f[i] = -f[i];
polypow(f, f, to_string(k), n + 1);
for(int i = 0; i <= n; ++ i) {
f[i] = f[i] * ifac[k] % P * fac[i] % P;
}
}
void Offset(ll *f, ll *h, int n, int c) { // f <-- h(x + c) 偏移 n次多项式
static ll g[N], d[N];
for(int i = 0; i <= n; ++ i) {
g[i] = fac[i] * h[i] % P;
d[i] = (i == 0 ? 1 : d[i - 1] * c % P);
}
for(int i = 0; i <= n; ++ i) d[i] = d[i] * ifac[i] % P;
reverse(d, d + n + 1);
polymul(f, g, d, n + 1, n + 1);
for(int i = 0; i <= n; ++ i) {
f[i] = f[i + n] * ifac[i] % P;
}
memset(f + n + 1, 0, n);
}
void Stirling1stRow(ll *f, int n) { // c(n, 0...n)
static int stk[30], tp;
static ll g[N];
tp = 0;
while(n) {
stk[++ tp] = n;
n >>= 1;
}
f[0] = 0, f[1] = 1;
n = 1;
while(-- tp) {
Offset(g, f, n, n);
polymul(f, f, g, n + 1, n + 1);
n <<= 1;
if(stk[tp] == n + 1) {
f[n + 1] = f[n];
for(int i = n; i >= 1; -- i) {
f[i] = (f[i] * n + f[i - 1]) % P;
}
++ n;
}
}
}
void Partition(ll *f, int n) {
memset(f, 0, (n + 1) * 8);
for(int i = 1; i <= n; ++ i) {
for(int j = i; j <= n; j += i) {
f[j] = (f[j] + inv[i]) % P;
}
}
polyexp(f, f, n + 1);
}
ll n, k, f[N];
int main() {
cin.tie(0)->sync_with_stdio(0);
init();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通