普通型母函数

生成函数(母函数)普通生成函数指数生成函数

1.普通生成函数用于解决多重集的组合问题

2.指数型母函数用于解决多重集的排列问题

母函数可以解决递归数列的通项问题:斐波那契数列、卡特兰数列等

普通母函数:

    构造母函数G(x), G(x) = a0 + a1*x + a2*clip_image002 + a3*clip_image002[4] +....+ an*clip_image002[6],  则称G(x)是数列a0,a1…an的母函数。

    通常普通母函数用来解多重集的组合问题,其思想就是构造一个函数来解决问题,一般过程如下:

    1.建立模型:

物品n种,每种数量分别为k1,k2,..kn个,每种物品又有一个属性值p1,p2,…pn,(如本题的字母价值),求属性值为m的物品组合方法数。(若数量ki无穷 也成立,即对应下面式子中第ki项的指数一直到无穷)

    2.构造母函数:

G(x)=(1+clip_image002[18]+clip_image002[20]clip_image002[22])(1+clip_image002[26]+clip_image002[28]+…clip_image002[30])…(1+clip_image002[32]+clip_image002[34]+…clip_image002[38])         (一)

          =a0 + a1*x + a2*clip_image002 + a3*clip_image002[4] +....+ akk*clip_image002[16]     (设kk=k1·p1+k2·p2+…kn·pn)         (二)

                  G(x)含义: ak 为属性值为k的组合方法数。

母函数利用的思想:

    1.把组合问题的加法法则和幂级数的乘幂对应起来。

    2.把离散数列和幂级数对应起来,把离散数列间的相互结合关系对应成为幂级数间的运算关系,最后由幂级数形式来

       确定离散数列的构造。

代码实现:

    求G(x)时一项一项累乘。先令G=1=(1+0*x+0*clip_image002[56]+…0*clip_image002[58]),再令G=G*(1+clip_image002[18]+clip_image002[20]clip_image002[22])得到形式(二)的式子…最后令G=G*(1+clip_image002[32]+clip_image002[34]+…clip_image002[38])。

普通母函数通常解决类似如下的问题:

给5张1元,4张2元,3张5元,要得到15元,有多少种组合?

某些时候会规定至少使用3张1元、1张2元、0张5元。

某些时候会规定有无数张1元、2元、5元。

……

解题过程:

首先要写出表达式,通常是多项式的乘积,每项由多个 x^{y} 组成。

通用表达式为:

(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();
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值