常生成函数

概述

在数学中,某个序列an的母函数(又称生成函数)是一种形式幂级数,其每一项的系数可以提供关于这个序列的信息。使用母函数解决问题的方法称为母函数方法。

序列 a a a定义为形式幂级数有以下形式

F ( x ) = ∑ n a n x n F(x)=\sum_{n}a_nx^n F(x)=nanxn

例如:

  • 序列 a = < 1 , 2 , 3 > a = <1, 2, 3> a=<1,2,3>的常生成函数是 1 + 2 x + 3 x 3 1 + 2x + 3x^3 1+2x+3x3
  • 序列 a = < 1 , 1 , 1 , 1... > a = <1,1,1,1...> a=<1,1,1,1...>的常生成函数是 ∑ n ≥ 0 x n \sum_{n \ge 0}x^n n0xn
  • 序列 a = < 1 , 3 , 5 , 7... > a = <1, 3, 5, 7...> a=<1,3,5,7...>的常生成函数是 ∑ n ≥ 0 ( 2 n + 1 ) x n \sum_{n \ge 0}(2n+1)x^n n0(2n+1)xn

常生成函数常用于多重集选择组合问题


应用

常生成函数通常解决以下问题:

给定非负整数 x 1 , x 2 , x 3 . . . , x k x_1,x_2,x_3...,x_k x1,x2,x3...,xk,求 x 1 + x 2 + x 3 . . . + x k = n x_1+x_2+x_3...+x_k=n x1+x2+x3...+xk=n有多少组解

以上述问题为例,我们隐式规定了 0 ≤ x i ≤ n 0\le x_i \le n 0xin,若我们为每个变量构造一个生成函数,则对于第i个数,它的生成函数为 f ( x ) = 1 + x + x 2 + x 3 . . . + x n f(x)=1+x+x^2+x^3...+x^n f(x)=1+x+x2+x3...+xn

若将所有的生成函数相乘,则 g ( x ) = ( 1 + x + x 2 + . . . + x n ) k g(x)=(1+x+x^2+...+x^n)^k g(x)=(1+x+x2+...+xn)k x n x^n xn的系数就是所求答案

这里给出几个常用替换式:

  • 1 + x + x 2 + x 3 . . . + x n = 1 1 − x 1+x+x^2+x^3...+x^n=\frac{1}{1-x} 1+x+x2+x3...+xn=1x1
  • 1 ( 1 − x ) k = ∑ n ≥ 0 C n + k − 1 k − 1 \frac{1}{(1-x)^k}=\sum_{n\ge0}C_{n+k-1}^{k-1} (1x)k1=n0Cn+k1k1

例题

背包

题目链接

题目描述

tacmon准备带大家去YZ一日游,他要带很多东西,一共有以下8种: 肥宅快乐水,鸡腿,鸡翅,鸡块,鸡汤,鸡蛋,大盘鸡,啤酒鸡 …emm…真香。

他要带得东西太多了,所以你理所当然的要帮他算带N个东西的方案数啦。
另外,在tacmon的眼里,所有的东西都是以“个”为单位的,而且每一种物品都有一些奇怪的限制。

  • tacmon最多会带1个肥宅快乐水(当然可以不带)
  • 他也认为大盘鸡和啤酒鸡太贵了,所以这两个东西他分别最多带2个和3个
  • 总所周知,鸡翅总是要成对出现的
  • tacmon认为偶数个鸡汤不好分,所以他准备带奇数个
  • 鸡块实在太好吃了,tacmon认为一个人一定会吃4个,所以他一定会带4的倍数个鸡块
  • tacmon最讨厌吃鸡腿了,所以他最多带一个鸡腿
  • 而鸡蛋,他准备带三的倍数个…

良心的tacmon觉得他的限制有点小多,所以他要好心的告诉大家,除了鸡汤,其他的东西不带也是符合要求的。

输入格式

一行一个整数N,表示tacmon要带的物品个数

输出格式

一行一个整数,即答案对 1 0 9 + 7 10^9+7 109+7取模的结果。

数据范围

1 ≤ N ≤ 1 0 18 1 \le N \le 10^{18} 1N1018

输入样例

5

输出样例

35

题解

本题使用容斥也能通过,这里讲解生成函数的做法,对于每一种食物,可以构造其生成函数,将八种食物累乘得到多项式 G ( x ) G(x) G(x) [ x n ] G ( x ) [x^n]G(x) [xn]G(x)就是答案,接下来是各类食物对应生成函数的化简:

  • 肥宅快乐水: 1 + x = 1 − x 2 1 − x 1+x=\frac{1-x^2}{1-x} 1+x=1x1x2
  • 大盘鸡: 1 + x + x 2 = 1 − x 3 1 − x 1+x+x^2=\frac{1-x^3}{1-x} 1+x+x2=1x1x3
  • 啤酒鸡: 1 + x + x 2 + x 3 = 1 − x 4 1 − x 1+x+x^2+x^3=\frac{1-x^4}{1-x} 1+x+x2+x3=1x1x4
  • 鸡翅: 1 + x 2 + x 4 + . . . = 1 1 − x 2 1+x^2+x^4+...=\frac{1}{1-x^2} 1+x2+x4+...=1x21
  • 鸡汤: x + x 3 + x 5 . . . = x 1 − x 2 x+x^3+x^5...=\frac{x}{1-x^2} x+x3+x5...=1x2x
  • 鸡块: 1 + x 4 + x 8 . . . = 1 1 − x 4 1+x^4+x^8...=\frac{1}{1-x^4} 1+x4+x8...=1x41
  • 鸡腿: 1 + x = 1 − x 2 1 − x 1+x=\frac{1-x^2}{1-x} 1+x=1x1x2
  • 鸡蛋: 1 + x 3 + x 6 . . . = 1 1 − x 3 1+x^3+x^6...=\frac{1}{1-x^3} 1+x3+x6...=1x31

因此 G ( x ) = x ( 1 − x ) 4 = x × ∑ n ≥ 0 C n + 3 3 x n = ∑ n ≥ 1 C n + 2 3 x n G(x)=\frac{x}{(1-x)^4}=x\times\sum_{n\ge0}C_{n+3}^{3}x^n=\sum_{n\ge 1}C_{n+2}^{3}x^n G(x)=(1x)4x=x×n0Cn+33xn=n1Cn+23xn

于是 [ x n ] G ( x ) = C n + 2 3 = ( n + 2 ) ( n + 1 ) ( n ) 6 [x^n]G(x)=C_{n+2}^{3}=\frac{(n+2)(n+1)(n)}{6} [xn]G(x)=Cn+23=6(n+2)(n+1)(n)

注意取模就能通过本题

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
int qpow(int a, int n, int mod) {
    int res = 1;
    while(n) {
        if(n & 1) res = res * a % mod;
        a = a * a % mod;
        n >>= 1;
    }
    return res;
}
signed main() {
    int n;
    cin >> n;
    cout << ((n + 2) % mod) * ((n + 1) % mod) % mod * (n % mod) % mod * qpow(6, mod - 2, mod) % mod ;
    return 0;
}

Devu and Flowers

题目链接

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5BdU7RAU-1658762589056)(…/…/image/4.png)]

题解

构造出每种花的生成函数 F i ( x ) = 1 + x + x 2 . . . + x f ( i ) = 1 − x f ( i ) + 1 1 − x F^i(x)=1+x+x^2...+x^{f(i)}=\frac{1-x^{f(i)+1}}{1-x} Fi(x)=1+x+x2...+xf(i)=1x1xf(i)+1

累乘后 G ( x ) = ∏ i ≤ n F ( i ) = ∏ i ≤ n ( 1 − x f ( i ) + 1 ) ( 1 − x ) n G(x)=\prod_{i\le n}F(i)=\frac{\prod_{i\le n}(1-x^{f(i)+1})}{(1-x)^n} G(x)=inF(i)=(1x)nin(1xf(i)+1)

最后答案为 [ x s ] G ( x ) [x^s]G(x) [xs]G(x)

若令 A ( x ) = ∏ i ≤ n ( 1 − x f ( i ) + 1 ) A(x)=\prod_{i\le n}(1-x^{f(i)+1}) A(x)=in(1xf(i)+1) B ( x ) = 1 ( 1 − x ) n = ∑ i ≥ 0 C i + n − 1 n − 1 x i B(x)=\frac{1}{(1-x)^n}=\sum_{i\ge 0}C_{i+n-1}^{n-1}x^i B(x)=(1x)n1=i0Ci+n1n1xi

则答案是由 A ( x ) 和 B ( x ) A(x)和B(x) A(x)B(x)共同得出

发现n很小,因此我们可以二进制枚举 A ( x ) A(x) A(x)的每一项,复杂度为 O ( 2 n ) O(2^n) O(2n),若此时枚举到的指数为 k k k,则我们只需要求 [ x s − k ] B ( x ) [x^{s-k}]B(x) [xsk]B(x)即可,容易得到这一项的值为 C s − k + n − 1 n − 1 C_{s-k+n-1}^{n-1} Csk+n1n1

对于取模且 a , b a,b a,b很大时,我们考虑lucas定理

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7, N = 25;
int a[N];
map<int, int> mp;
int qpow(int a, int n, int mod) {
    int res = 1;
    while(n) {
        if(n & 1) res = (res % mod) * (a % mod) % mod;
        a = (a % mod) * (a % mod) % mod;
        n >>= 1;
    }
    return res;
}
int C(int a, int b, int mod) {
    int fz = 1, fm = 1;
    for(int i = 1, j = a; i <= b; i ++, j -- ) {
        fm *= (i % mod);
        fm %= mod;
        fz *= (j % mod);
        fz %= mod;
    }
    return fz * qpow(fm, mod - 2, mod) % mod;
}
int lucas(int a, int b, int mod) {
    if(a < b) return 0;
    if(a < mod && b < mod) return C(a, b, mod);
    return lucas(a % mod, b % mod, mod) * lucas(a / mod, b / mod, mod) % mod;
}
signed main() {
    int n, s;
    cin >> n >> s;
    //cout << C(10, 3) << endl << C(5, 3) << endl;
    for(int i = 0; i < n; i ++ ) cin >> a[i];
    for(int i = 0; i < (1 << n); i ++ ) {
        int sum = 0;
        int k = 1;
        for(int j = 0; j < n; j ++ ) {
            if((i >> j) & 1) {
                sum += (a[j] + 1);
                k = -k;
            }
        }
        mp[sum] += k;
    }
    int res = 0;
    for(auto t : mp) {
        int k = t.first;
        int v = t.second;
        //cout << k << " " << v << endl;
        if(k > s) continue;
        res += v * (lucas(s - k + n - 1, n - 1, mod) % mod) % mod;
        res %= mod;
    }
    cout << ((res % mod) + mod) % mod;
    return 0;
}

[CEOI2004] Sweets

题目链接

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xsg6ZIzg-1658762589061)(…/…/image/5.png)]

题解

本题和上一题很相似,不同的是总数是一段区间,若枚举总数则复杂度达到了 O ( ( b − a ) n 2 n ) O((b-a)n2^n) O((ba)n2n),显然超时,因此需要优化

r e s = ∑ i = a i = b ∑ j ( [ x j ] A ( x ) ) C i − j + n − 1 n − 1 res = \sum_{i=a}^{i=b}\sum_{j}([x^j]A(x))C_{i-j+n-1}^{n-1} res=i=ai=bj([xj]A(x))Cij+n1n1

r e s res res的得出同上题

接下来讲讲怎么优化

变更计算顺序,将枚举i变为枚举j

r e s = ∑ j ( [ x j ] A ( x ) ) ∑ i = a i = b C i − j + n − 1 n − 1 res=\sum_{j}([x^j]A(x))\sum_{i=a}^{i=b}C_{i-j+n-1}^{n-1} res=j([xj]A(x))i=ai=bCij+n1n1

我们知道 C a + 1 b + 1 = C a b + 1 + C a b C_{a+1}^{b+1}=C_{a}^{b+1}+C_{a}^{b} Ca+1b+1=Cab+1+Cab

因此原式可化简为

r e s = ∑ j ( [ x j ] A ( x ) ) ( C b − j + n n − C m a x ( a , j ) − j + n − 1 n ) res=\sum_{j}([x^j]A(x))(C_{b-j+n}^{n}-C_{max(a,j)-j+n-1}^{n}) res=j([xj]A(x))(Cbj+nnCmax(a,j)j+n1n)

m a x ( a , j ) max(a,j) max(a,j)是因为当 j > a j>a j>a时并未是无意义,这一部分的答案也要被记录

接下来就是另一个问题了

本题的模数是2004,没有逆元,扩展卢卡斯定理可以做,但这里有个小技巧

我们发现 n n n只有10,而组合数计算时的分母是 n ! n! n!,因此我们可以在求组合数时令分子计算时对 2004 × n ! 2004 \times n! 2004×n!取模,最后在除以分母

这样这道题就被完美解决了

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 2004, N = 15;
int n, a, b;
int ab[N];
map<int, int> mp;
int fac[N];
void init(int n) {
    fac[0] = 1;
    for(int i = 1; i <= n; i ++ ) {
        fac[i] = fac[i - 1] * i;  
    }
}

int C(int a, int b, int p) {
    int fz = 1, fm = 1;
    for(int i = 1, j = a; i <= b; i ++ , j -- ) {
        fz *= j;
        fz %= p;
    }
    //cout << fz << " " << fm << endl;
    return (fz / fac[n]) % mod;
}

signed main() {
    cin >> n >> a >> b;
    for(int i = 0; i < n; i ++ ) cin >> ab[i];
    init(n + 1);
    for(int i = 0; i < (1 << n); i ++ ) {
        int sum = 0;
        int k = 1;
        for(int j = 0; j < n; j ++ ) {
            if((i >> j) & 1) {
                sum += (ab[j] + 1);
                k = -k;
            }
        }    
        mp[sum] += k;
    }
    int res = 0;
    for(auto [k, v] : mp) {
        if(k > b) break;
        int kk = max(k, a);
        res += v % mod * (C(b - k + n, n, mod * fac[n]) - C(kk - k + n - 1, n, mod * fac[n])) % mod;
        res %= mod;
    }
    cout << (res % mod + mod) % mod << endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值