也叫母函数,常用来解决组合方面的问题。
一个常见的例子如下:
有 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 的指数就表示了组成的面值,系数就表示了方案数。
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
#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;
}
先不考虑 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
#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;
}