ACM算法总结 生成函数



也叫母函数,常用来解决组合方面的问题。

一个常见的例子如下:

有 n 种硬币,每一种硬币的面值为 a i a_i ai ,数目为 c i c_i ci ,问用这些硬币可以组合出哪些数值的面值,并且各自有多少种组合方法。

这显然是一个背包问题,但是我们考虑用生成函数来解决。一般来说普通的母函数为这样的形式 a 0 + a 1 x + a 2 x 2 + . . . + a n x n a_0+a_1x+a_2x^2+...+a_nx^n a0+a1x+a2x2+...+anxn ,系数表示方案数,指数表示方案的状态。对于上面那个问题,可以转化为这样一个多项式乘法:
∏ i = 1 n ∑ j = 0 c i x j a i = ( 1 + x a 1 + x 2 a 1 + ⋯ + x c 1 a 1 ) ( 1 + x a 2 + ⋯ + x c 2 a 2 ) ⋯ ( 1 + x a n + ⋯ + x c n a n ) \begin{aligned} &\prod\limits_{i=1}^{n}\sum\limits_{j=0}^{c_i}x^{ja_i} \\ =& (1+x^{a_1}+x^{2a_1}+\cdots+x^{c_1a_1})(1+x^{a_2}+\cdots+x^{c_2a_2})\cdots(1+x^{a_n}+\cdots+x^{c_na_n}) \end{aligned} =i=1nj=0cixjai(1+xa1+x2a1++xc1a1)(1+xa2++xc2a2)(1+xan++xcnan)
把它展开之后,每个 x 的指数就表示了组成的面值,系数就表示了方案数。


  • 一道例题 Ignatius and the Princess III :给出正整数 N( 1 ≤ n ≤ 120 1\le n \le 120 1n120),问有多少种本质不同的方案可以使得 a 1 + a 2 + ⋯ a m = N a_1+a_2+\cdots a_m=N a1+a2+am=N,并且满足 m ≤ N , a i > 0 m\le N,a_i>0 mN,ai>0 。这里本质不同指的是 1 2 11 1 2 算同一种方案。

完全背包肯定是可以做的,生成函数的话可以考虑计算一个这样的式子: ( 1 + x + x 2 + ⋯   ) ( 1 + x 2 + x 4 + ⋯   ) ⋯ ( 1 + x N ) (1+x+x^2+\cdots)(1+x^2+x^4+\cdots)\cdots(1+x^N) (1+x+x2+)(1+x2+x4+)(1+xN) ,然后就可以了。

代码:

#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> P;
int read()
{
    int x=0,flag=1; char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=120;
int f[maxn+5],n,g[maxn+5];

int main()
{
    REP(i,0,maxn) f[i]=1;
    REP(k,2,maxn)
    {
        REP(i,0,maxn) for(int j=0;j+i<=maxn;j+=k)
            g[j+i]+=f[i];
        REP(i,0,maxn) f[i]=g[i],g[i]=0;
    }
    while(~scanf("%d",&n))
        printf("%d\n",f[n]);

    return 0;
}


  • 再比如说一道稍微复杂一些的题目 Easy ,题意如下:给定三个正整数 N,M 和 K( 1 ≤ K ≤ N , M ≤ 1 0 6 1\le K\le N,M \le 10^6 1KN,M106),如果找到两个正整数序列 A 和 B,满足 ∑ i = 1 K A i = N \sum\limits_{i=1}^KA_i=N i=1KAi=N 以及 ∑ i = 1 K B i = M \sum\limits_{i=1}^KB_i=M i=1KBi=M ,那么就可以获得收益 ∏ i = 1 K m i n ( A i , B i ) \prod\limits_{i=1}^K min(A_i,B_i) i=1Kmin(Ai,Bi) ,问可以获得的总收益是多少?

先不考虑 m i n ( A i , B i ) min(A_i,B_i) min(Ai,Bi) 的收益,只考虑前面的条件限制,我们很容易列出生成函数 ( x + x 2 + ⋯   ) K ( y + y 2 + ⋯   ) K = ( x y ( 1 − x ) ( 1 − y ) ) K (x+x^2+\cdots)^K(y+y^2+\cdots)^K=(\frac{xy}{(1-x)(1-y)})^K (x+x2+)K(y+y2+)K=((1x)(1y)xy)K ,然后其中 x N y M x^Ny^M xNyM 的系数就是方案数,现在我们需要考虑的是对于每个 x i y j x^iy^j xiyj,给它赋予一个系数 m i n ( i , j ) min(i,j) min(i,j) 。因为每个 x i y j x^iy^j xiyj 实际上只是一个方案,如果我们可以把它变成 m i n ( i , j ) min(i,j) min(i,j) 个方案那就行了。所以把原生成函数转换为 ( x + x 2 + ⋯   ) K ( y + y 2 + ⋯   ) K ( 1 + x y + ( x y ) 2 + ⋯   ) K = ( x y ( 1 − x ) ( 1 − y ) ( 1 − x y ) ) K (x+x^2+\cdots)^K(y+y^2+\cdots)^K(1+xy+(xy)^2+\cdots)^K=(\frac{xy}{(1-x)(1-y)(1-xy)})^K (x+x2+)K(y+y2+)K(1+xy+(xy)2+)K=((1x)(1y)(1xy)xy)K ,这样每个 x i y j x^iy^j xiyj 实际上也可以由 x i − 1 y j − 1 , ⋯   , x 1 y j − i + 1 x^{i-1}y^{j-1},\cdots,x^{1}y^{j-i+1} xi1yj1,,x1yji+1 搭配 x y xy xy 的某个幂组合出来,所以原来的一个方案就变成了要求的系数。

( x y ( 1 − x ) ( 1 − y ) ( 1 − x y ) ) K (\frac{xy}{(1-x)(1-y)(1-xy)})^K ((1x)(1y)(1xy)xy)K 这个生成函数下,我们只需要计算出 x N y M x^Ny^M xNyM 的系数,这就是我们需要的答案。分子已经有了 x K y K x^Ky^K xKyK ,所以我们需要计算出 ( 1 ( 1 − x ) ( 1 − y ) ( 1 − x y ) ) K (\frac{1}{(1-x)(1-y)(1-xy)})^K ((1x)(1y)(1xy)1)K x N − K y M − K x^{N-K}y^{M-K} xNKyMK 的系数,不难看出该式子是三个级数的乘积,对于 1 ( 1 − x ) n \frac{1}{(1-x)^n} (1x)n1 中某一项的系数的求法,有如下公式:

根据广义二项式定理,有如下公式:

( 1 + x ) − n = ∑ k = 0 ∞ ( − 1 ) k C n + k − 1 k x k (1+x)^{-n}=\sum\limits_{k=0}^{\infty}(-1)^kC_{n+k-1}^kx^k (1+x)n=k=0(1)kCn+k1kxk

( 1 − x ) − n = ∑ k = 0 ∞ C n + k − 1 k x k (1-x)^{-n}=\sum\limits_{k=0}^{\infty}C_{n+k-1}^kx^k (1x)n=k=0Cn+k1kxk

所以,原问题的答案就是 a n s = ∑ i = 0 N − K C K + i − 1 i C K + N − K − i − 1 N − K − i C K + M − K − i − 1 M − K − i ans=\sum\limits_{i=0}^{N-K}C_{K+i-1}^i C_{K+N-K-i-1}^{N-K-i}C_{K+M-K-i-1}^{M-K-i} ans=i=0NKCK+i1iCK+NKi1NKiCK+MKi1MKi

代码:

#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> P;
int read()
{
    int x=0,flag=1; char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=1e6+5,N=1e6;
const LL M=998244353;
LL inv[maxn],jie[maxn];

LL ksm(LL x,LL n)
{
    LL ret=1;
    while(n)
    {
        if(n&1) ret=ret*x%M;
        x=x*x%M;
        n>>=1;
    }
    return ret;
}

LL C(LL n,LL m)
{
    if(n<0 || m<0 || n<m) return 0;
    if(!n || !m) return 1;
    return jie[n]*inv[m]%M*inv[n-m]%M;
}

int main()
{
    jie[0]=1;
    REP(i,1,N) jie[i]=jie[i-1]*i%M;
	REP(i,0,N) inv[i]=ksm(jie[i],M-2);
	int T=read();
	while(T--)
    {
        int n=read(),m=read(),k=read();
        if(n>m) swap(n,m);
        LL ans=0;
        REP(i,0,n-k)
        {
            LL x1=C(k+i-1,i);
            LL x2=C(k+n-k-i-1,n-k-i);
            LL x3=C(k+m-k-i-1,m-k-i);
            ans+=x1*x2%M*x3%M;
        }
        printf("%lld\n",ans%M);
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值