也叫母函数,常用来解决组合方面的问题。
一个常见的例子如下:
有 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=1∏nj=0∑cixjai(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
1≤n≤120),问有多少种本质不同的方案可以使得
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
m≤N,ai>0 。这里本质不同指的是
1 2 1
和1 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 1≤K≤N,M≤106),如果找到两个正整数序列 A 和 B,满足 ∑ i = 1 K A i = N \sum\limits_{i=1}^KA_i=N i=1∑KAi=N 以及 ∑ i = 1 K B i = M \sum\limits_{i=1}^KB_i=M i=1∑KBi=M ,那么就可以获得收益 ∏ i = 1 K m i n ( A i , B i ) \prod\limits_{i=1}^K min(A_i,B_i) i=1∏Kmin(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=((1−x)(1−y)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=((1−x)(1−y)(1−xy)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} xi−1yj−1,⋯,x1yj−i+1 搭配 x y xy xy 的某个幂组合出来,所以原来的一个方案就变成了要求的系数。
在 ( x y ( 1 − x ) ( 1 − y ) ( 1 − x y ) ) K (\frac{xy}{(1-x)(1-y)(1-xy)})^K ((1−x)(1−y)(1−xy)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 ((1−x)(1−y)(1−xy)1)K 中 x N − K y M − K x^{N-K}y^{M-K} xN−KyM−K 的系数,不难看出该式子是三个级数的乘积,对于 1 ( 1 − x ) n \frac{1}{(1-x)^n} (1−x)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+k−1kxk
( 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 (1−x)−n=k=0∑∞Cn+k−1kxk
所以,原问题的答案就是 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=0∑N−KCK+i−1iCK+N−K−i−1N−K−iCK+M−K−i−1M−K−i 。
代码:
#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;
}