生成函数(母函数)有普通生成函数和指数生成函数:
1.普通生成函数用于解决多重集的组合问题
2.指数型母函数用于解决多重集的排列问题
母函数可以解决递归数列的通项问题:斐波那契数列、卡特兰数列等
普通母函数:
构造母函数G(x), G(x) = a0 + a1*x + a2* + a3* +....+ an*, 则称G(x)是数列a0,a1…an的母函数。
通常普通母函数用来解多重集的组合问题,其思想就是构造一个函数来解决问题,一般过程如下:
1.建立模型:
物品n种,每种数量分别为k1,k2,..kn个,每种物品又有一个属性值p1,p2,…pn,(如本题的字母价值),求属性值和为m的物品组合方法数。(若数量ki无穷 也成立,即对应下面式子中第ki项的指数一直到无穷)
2.构造母函数:
G(x)=(1++…)(1+++…)…(1+++…) (一)
=a0 + a1*x + a2* + a3* +....+ akk* (设kk=k1·p1+k2·p2+…kn·pn) (二)
G(x)含义: ak 为属性值和为k的组合方法数。
母函数利用的思想:
1.把组合问题的加法法则和幂级数的乘幂对应起来。
2.把离散数列和幂级数对应起来,把离散数列间的相互结合关系对应成为幂级数间的运算关系,最后由幂级数形式来
确定离散数列的构造。
代码实现:
求G(x)时一项一项累乘。先令G=1=(1+0*x+0*+…0*),再令G=G*(1++…)得到形式(二)的式子…最后令G=G*(1+++…)。
普通母函数通常解决类似如下的问题:
给5张1元,4张2元,3张5元,要得到15元,有多少种组合?
某些时候会规定至少使用3张1元、1张2元、0张5元。
某些时候会规定有无数张1元、2元、5元。
……
解题过程:
首先要写出表达式,通常是多项式的乘积,每项由多个 组成。
通用表达式为:
(x^(v[0]*n1[0])+x^(v[0]*(n1[0]+1))+x^(v[0]*(n1[0]+2))+...+x^(v[0]*n2[0]))
(x^(v[1]*n1[1])+x^(v[1]*(n1[1]+1))+x^(v[1]*(n1[1]+2))+...+x^(v[1]*n2[1]))
...............
(x^(v[K]*n1[K])+x^(v[K]*(n1[K]+1))+x^(v[K]*(n1[K]+2))+...+x^(v[K]*n2[K]))
K对应具体问题中物品的种类数。
V[i]表示该乘积表达式第 i 个因子的权重,对应具体问题的每个物品的价值或者权重
n1[i]表示该乘积表达式第 i 个因子的起始系数,对应于具体问题中的每个物品的最少个数,即最少要取多少个
n2[i]表示该乘积表达式第i个因子的终止系数,对应具体问题中的每个物品的最多个数,即最多要取多少个
解题的关键是要确定V,N1,N2数组的值
模板
//a为计算结果,b为中间结果
int a[MAX],b[MAX];
memset(a,0,sizeof a);
a[0]=1;
for(int i=1;i<=k;i++)//循环每个因子
{
memset(b,0,sizeof(b));
for(int j=n1[i];j<=n2[i]&&j*v[i]<=p;j++)//循环每个因子的每一项
for(int k=0;k+j*v[i]<=p;k++)//循环a的每一项
b[k+j*v[i]]+=a[k];//把结果加到对应位
memccpy(a,b,sizeof b);//b赋值给a
}
p是可能的最大指数,拿钞票组合的题来说,如果求15元有多少种组合,那么p就是15;如果问最小的不能拼出的数值,那么p就是所有钱加起来的和。
用一个last变量记录目前最大的指数,这样只需要在0--last上计算
改进模板
//初始化a,因为有last,所以这里无需初始化其他位
int last=0;
a[0]=1;
for(int i=1;i<=n;i++) {//循环每个因子
int next=min(last+n2[i]*v[i],p);//计算下一次的last
for(int j=n1[i];j<=n2[i]&&j*v[i]<=next;j++)//循环每个因子的每一项
for(int k=0;k<=last&&k+j*v[i]<=next;k++)//循环a的每一项
b[k+j*v[i]]+=a[k];//把结果加到对应位
for(int k=0;k<maxn;k++) {//赋值
a[k]=b[k]%mod;
b[k]=0;
}
last=next;//更新last
}
例题
一、hdu 1085和hdu 1171两题套用了第二个模板,省略了代码中二三层循环里关于last2的条件(其实也可以加上)。
详见:
hdu1085 :http://blog.csdn.net/xiaofei_it/article/details/17041467
import java.util.*;
import java.math.*;
public class Main {
public static int maxn=8010;
public static void main(String[] args) {
int[] a=new int[maxn];//保存结果,a[i]表示组成i种水果的方案数为a[i]
int[] b=new int[maxn];//中间结果
int[] n1=new int[maxn];//第i种水果最少的个数
int[] n2=new int[maxn];//第i种水果最多的个数
int[] v=new int[maxn];//第i种水果的价值
Scanner cin=new Scanner(System.in);
//int T=cin.nextInt();
while(cin.hasNext()) {
for(int i=0;i<maxn;i++) {
a[i]=0;
b[i]=0;
n1[i]=0;
n2[i]=0;
}
v[1]=1;v[2]=2;v[3]=5;
int sum=0;
for(int i=1;i<=3;i++) {
n2[i]=cin.nextInt();
sum+=n2[i];
}
if(sum==0) break;
a[0]=1;
int p=0;
//要求最小不能拼出的钱,p就是所有钱加起来的和
for(int i=1;i<=3;i++)
p=p+v[i]*n2[i];
for(int i=1;i<=3;i++) {
for(int k=0;k<maxn;k++)
b[k]=0;
for(int j=n1[i];j<=n2[i]&&j*v[i]<=p;j++)
for(int k=0;k+j*v[i]<=p;k++)
b[k+j*v[i]]+=a[k];
for(int k=0;k<maxn;k++)
a[k]=b[k];
}
int ans=0;
//遍历找出方案数为0的钱币组合
for(int i=1;i<maxn;i++)
if(a[i]==0) {
ans=i;
break;
}
System.out.println(ans);
}
cin.close();
}
}
hdu 1171:http://blog.csdn.net/xiaofei_it/article/details/17041709
import java.util.*;
import java.math.*;
public class Main {
public static int maxn=250000,maxm=60;
public static void main(String[] args) {
int[] a=new int[maxn];//保存结果,a[i]表示组成i种水果的方案数为a[i]
int[] b=new int[maxn];//中间结果
int[] n1=new int[maxm];//第i种水果最少的个数
int[] n2=new int[maxm];//第i种水果最多的个数
int[] v=new int[maxm];//第i种水果的价值
Scanner cin=new Scanner(System.in);
//int T=cin.nextInt();
while(cin.hasNext()) {
for(int i=0;i<maxn;i++) {
a[i]=0;
b[i]=0;
}
for(int i=0;i<maxm;i++) {
n1[i]=0;
n2[i]=0;
v[i]=0;
}
int n=cin.nextInt();
if(n<0) break;
int sum=0;
for(int i=1;i<=n;i++) {
v[i]=cin.nextInt();
n2[i]=cin.nextInt();
sum+=v[i]*n2[i];
}
int p=sum;
//if(sum%2==1) p++;
a[0]=1;
for(int i=1;i<=n;i++) {
for(int k=0;k<maxn;k++)
b[k]=0;
for(int j=n1[i];j<=n2[i]&&j*v[i]<=p;j++)
for(int k=0;k+j*v[i]<=p;k++)
b[k+j*v[i]]+=a[k];
for(int k=0;k<maxn;k++)
a[k]=b[k];
}
int ans=0;
for(int i=p/2;i>=0;i--) {
if(a[i]!=0) {
ans=i;
break;
}
}
System.out.println(p-ans+" "+ans);
}
cin.close();
}
}
二、hdu 1398套用了第一个模板,因为n2中每一项为无穷大,所以n2数组就省略了。
详见:
hdu 1398:http://blog.csdn.net/xiaofei_it/article/details/17041815
import java.math.*;
import java.util.*;
public class Main {
public static int maxn=300*300+10;
static int[] a=new int[maxn];
static int[] b=new int[maxn];
static int[] v=new int[30];
static void init() {
for(int i=0;i<maxn;i++) {
a[i]=0;b[i]=0;
}
for(int i=1;i<=17;i++)
v[i]=i*i;
a[0]=1;
for(int i=1;i<=17;i++) {
for(int j=0;j*v[i]<=300;j++)
for(int k=0;k+j*v[i]<=300;k++)
b[k+j*v[i]]+=a[k];
for(int k=0;k<maxn;k++) {
a[k]=b[k];
b[k]=0;
}
}
}
public static void main(String[] args) {
Scanner cin=new Scanner(System.in);
init();
while(cin.hasNext()) {
int n=cin.nextInt();
if(n==0) break;
System.out.println(a[n]);
}
cin.close();
}
}
三、hdu 2079、hdu 2082和hdu 2110三题直接套用了第二个模板。
详见:
hdu2079
http://blog.csdn.net/xiaofei_it/article/details/17042045
import java.math.*;
import java.util.*;
public class Main {
public static int maxn=400;
static int[] a=new int[maxn];
static int[] b=new int[maxn];
static int[] v=new int[maxn];
static int[] n1=new int[maxn];
static int[] n2=new int[maxn];
static void init() {
for(int i=0;i<maxn;i++) {
a[i]=0;b[i]=0;
v[i]=0;
n1[i]=0;n2[i]=0;
}
}
public static void main(String[] args) {
Scanner cin=new Scanner(System.in);
int T=cin.nextInt();
while(T!=0) {
T--;
init();
int p=cin.nextInt();
int n=cin.nextInt();
for(int i=1;i<=n;i++) {
v[i]=cin.nextInt();
n2[i]=cin.nextInt();
}
a[0]=1;
for(int i=1;i<=n;i++) {
for(int j=n1[i];j<=n2[i]&&j*v[i]<=p;j++)
for(int k=0;k+j*v[i]<=p;k++)
b[k+j*v[i]]+=a[k];
for(int k=0;k<maxn;k++) {
a[k]=b[k];
b[k]=0;
}
}
System.out.println(a[p]);
}
cin.close();
}
}
hdu2082:
http://blog.csdn.net/xiaofei_it/article/details/17042253
import java.util.*;
import java.math.*;
public class Main {
public static int maxn=110;
public static void main(String[] args) {
int[] a=new int[maxn];//保存结果,a[i]表示组成i种水果的方案数为a[i]
int[] b=new int[maxn];//中间结果
int[] n1=new int[maxn];//第i种水果最少的个数
int[] n2=new int[maxn];//第i种水果最多的个数
int[] v=new int[maxn];//第i种水果的价值
Scanner cin=new Scanner(System.in);
int T=cin.nextInt();
while(T!=0) {
T--;
for(int i=0;i<maxn;i++) {
a[i]=0;
b[i]=0;
n1[i]=0;
n2[i]=0;
v[i]=i;
}
for(int i=1;i<=26;i++)
n2[i]=cin.nextInt();
a[0]=1;
int p=50;
for(int i=1;i<=26;i++) {
for(int k=0;k<maxn;k++)
b[k]=0;
for(int j=n1[i];j<=n2[i]&&j*v[i]<=p;j++)
for(int k=0;k+j*v[i]<=p;k++)
b[k+v[i]*j]+=a[k];
for(int k=0;k<maxn;k++)
a[k]=b[k];
}
long ans=0;
for(int i=1;i<=p;i++)
ans+=a[i];
System.out.println(ans);
}
cin.close();
}
}
hdu 2110:
http://blog.csdn.net/xiaofei_it/article/details/17042421
import java.math.*;
import java.util.*;
public class Main {
static int mod=10000;
public static int maxn=10000+7;
static long[] a=new long[maxn];
static long[] b=new long[maxn];
static int[] v=new int[maxn];
static int[] n1=new int[maxn];
static int[] n2=new int[maxn];
static int min(int a,int b) {
return a<b?a:b;
}
static void init() {
for(int i=0;i<maxn;i++) {
a[i]=0;b[i]=0;
v[i]=0;
n1[i]=0;n2[i]=0;
}
}
public static void main(String[] args) {
Scanner cin=new Scanner(System.in);
//int T=cin.nextInt();
while(cin.hasNext()) {
init();
int n=cin.nextInt();
if(n==0) break;
int p=0;
for(int i=1;i<=n;i++) {
v[i]=cin.nextInt();
n2[i]=cin.nextInt();
p+=v[i]*n2[i];
}
if(p%3!=0) {
System.out.println("sorry");
continue;
}
p/=3;
int last=0;
a[0]=1;
for(int i=1;i<=n;i++) {
int next=min(last+n2[i]*v[i],p);//计算下一次的last
for(int j=n1[i];j<=n2[i]&&j*v[i]<=next;j++)
for(int k=0;k<=last&&k+j*v[i]<=next;k++)
b[k+j*v[i]]+=a[k];
for(int k=0;k<maxn;k++) {
a[k]=b[k]%mod;
b[k]=0;
}
last=next;//更新last
}
if(a[p]!=0)
System.out.println(a[p]%mod);
else
System.out.println("sorry");
}
cin.close();
}
}
另外,至于什么时候用第一个模板,什么时候用第二个模板,就看题目规模。 通常情况下,第一个模板就够用了,上面的那些用第二个模板的题目用第一个模板同样能AC。 但如果数据规模比较大(通常不会有这种情况),就要使用第二个模板了。 以上题目n1均为0。
四、hdu 2152是一道n1不为0的题目,我在这里分别套用第一个和第二个模板解题。
详见: hdu 2152:http://blog.csdn.net/xiaofei_it/article/details/17042497
C++
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=130;
int a[maxn],b[maxn];
int n1[maxn],n2[maxn];
int v[maxn];
void init()
{
memset(a,0,sizeof a);
memset(b,0,sizeof b);
memset(n1,0,sizeof n1);
memset(n2,0,sizeof n2);
}
int main()
{
for(int i=0;i<maxn;i++)
v[i]=1;
int n,m;
while(scanf("%d%d",&n,&m)!=EOF){
init();
for(int i=1;i<=n;i++)
scanf("%d%d",&n1[i],&n2[i]);
a[0]=1;
for(int i=1;i<=n;i++)
{
memset(b,0,sizeof b);
for(int j=n1[i];j<=n2[i]&&j*v[i]<=m;j++)
for(int k=0;k+j*v[i]<=m;k++)
b[k+v[i]*j]+=a[k];
//memcpy(a,b,sizeof b);
for(int k=0;k<maxn;k++)
a[k]=b[k];
}
printf("%d\n",a[m]);
}
}
Java
import java.util.*;
import java.math.*;
public class Main {
public static int maxn=110;
public static void main(String[] args) {
int[] a=new int[maxn];//保存结果,a[i]表示组成i种水果的方案数为a[i]
int[] b=new int[maxn];//中间结果
int[] n1=new int[maxn];//第i种水果最少的个数
int[] n2=new int[maxn];//第i种水果最多的个数
int[] v=new int[maxn];//第i种水果的价值
Scanner cin=new Scanner(System.in);
//int T=cin.nextInt();
while(cin.hasNext()) {
for(int i=0;i<maxn;i++) {
a[i]=0;b[i]=0;n1[i]=0;n2[i]=0;
v[i]=1;
}
int n=cin.nextInt();
int m=cin.nextInt();
for(int i=1;i<=n;i++) {
n1[i]=cin.nextInt();
n2[i]=cin.nextInt();
}
a[0]=1;
for(int i=1;i<=n;i++) {//循环每一个因子
for(int k=0;k<maxn;k++)
b[k]=0;
for(int j=n1[i];j<=n2[i]&&j*v[i]<=m;j++)//循环每个因子的每一项
for(int k=0;k+j*v[i]<=m;k++)//循环a的每一项
b[k+j*v[i]]+=a[k];//把结果加到对应的位
for(int k=0;k<maxn;k++)//b赋值给a
a[k]=b[k];
}
System.out.println(a[m]);
}
cin.close();
}
}