数论三(母函数)

首先简要介绍下母函数是什么,然后说下它的应用场景,及相关题目。

1母函数

对于序列a0,a1,a2...构造一个函数G(x)=a0+a1x+a2x^2+...,我们成G(x)是序列a0,a1,a2,...的母函数。

例如:

(1+x)n是序列C(n,0),C(n,1),...,C(n,n)的母函数。 如若已知序列a0,a1,a2,…则对应的母函数G(x)便可根据定义给出。反之,如若已经求得序列的母函数G(x),则该序列也随之确定。 
 序列a0,a1,a2,…可记为{an} 。

2应用场景

a 邮票或者硬币的组合问题

eg:若有1克、2克、3克、4克的砝码各一枚,能称出哪几种重量?各有几种可能方案? 

考虑构造母函数。如果用x的指数表示称出的重量,则: 1个1克的砝码可以用函数1+x表示,1个2克的砝码可以用函数1+x2表示, 1个3克的砝码可以用函数1+x3表示,1个4克的砝码可以用函数1+x4表示,几种砝码可以称重的组合,就可以用这四个函数的乘积表示,(1+x)(1+x2)(1+x3)(1+x4)=(1+x+x2+x3)(1+x3+x4+x7)

=1+x+x2+2x3+2x4+2x5+2x6+2x7+x8+x9+x10 ,从上面的函数知道:可称出从1克到10克,系数便是方案数。并且我们可以保证无重复组合,及3 4 组合成7,且不会统计4 3 组合成7的情况,因为我们是从低次幂往高次幂统计的。有的题目迷惑你说不能求重复的,其实我们按照这种方法已经排除了重复的了。

b 整数拆分

例如4=4,4=1+3,4=2+2,4=1+1+1+1,这样4的拆分方式为4种。这个题其实和上个问题类似,只是逆向而已,我们仍然可以用母函数解决,构造数字1对应的x的那个项为(1+x+x^2+x^3+...)一直让它的幂达到给的要拆分的数字,数字2对应的为(1+x^2+x^4+....),一次类推,知道(1+x^num)。

注意的地方,有的限制砝码(邮票,硬币,数字)的个数,这个的话只需要在我们的母函数模板中多加个判断语句就可以了。

模板如下:

//a1[i]保存的是当前已经计算出来的前n个括号相乘的结果,及a1[0]指数为0的系数,a1[1]指数为1的系数,。。。
//a2[i]保存的是已经计算出来的结果乘以下一个括号中的每项的结果
for(i=0;i<=sum;i++)
{
    a1[i]=0;
    a2[i]=0;
}
//将第一个括号中的可以达到的指数的对应系数置1
for(i=0;i<=n1;i++)
    a1[i]=1;
for(i=1;i<n;i++)//从第二个括号到最后一个括号
{

   //模拟我们手工计算的过程
    for(j=0;j<=sum;j++)
    {
	for(k=0;k+j<=sum;k+=value[i])
	{
	    a2[k+j]+=a1[j];
	}
    }
    for(j=0;j<=sum;j++)
    {
	a1[j]=a2[j];
	a2[j]=0;
    }
}
cout<<a1[sum];

3对应例题:


hdu1398

有一堆硬币,1分的,4分的,9分的,。。。,17^2分的,每一种有若干个,现在给出一个分值,问组成这个分值的
方法有多少种?
构造母函数,1分的1+x+x^2+..,4分的1+x^4+x^8+...,9分的1+x^9+x^18+...,一直到17^2,G(x)=
(1+x+x^2+.)*(1+x^4+x^8+...)*(1+x^9+x^18+..)*...*(1+x^289+x^578+...),我们的目的是计算出来x^n的系数。
利用程序模拟我们计算的过程即可得到结果。
源码如下(java版):

import java.util.Scanner;
public class Main {
	public static final int N=301;
	public static final int M=17;
	public static void main(String args[])
	{
		int a1[]=new int[N],a2[]=new int[N],i,j,k,n;
		Scanner in=new Scanner(System.in);
		boolean flag=true;
		while(flag)
		{
			n=in.nextInt();
			if(0 == n)
				break;
			for(i=0;i<=n;i++)
			{
				a1[i]=1;
				a2[i]=0;
			}
			for(i=2;i<=M;i++)
			{
				for(j=0;j<=n;j++)
				{
					for(k=0;k+j<=n;k+=i*i)
						a2[k+j]=a2[k+j]+a1[j];
				}
				for(j=0;j<=n;j++)
				{
					a1[j]=a2[j];
					a2[j]=0;
				}
			}
			System.out.println(a1[n]);
		}	
	}
}

 hdu1028
整数拆分问题,母函数的应用,输入一个整数n,构造母函数G(x)=(1+x+x^2+..)*(1+x^2+x^4+..)*(1+x^3+x^6+...)*...*(1+x^n),题目给出了一个限制
说4=1+3和3+1是同一种方式,我还以为需要修改程序,仔细一想,利用模板求出来的根本不存在3+1的情况,因为我们每次都是从低次幂往高次幂走,所以和上个题差不多。
源码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
public class Main {
	public static final int N=121;
	public static final int M=17;
	public static void main(String args[])
	{
		int a1[]=new int[N],a2[]=new int[N],i,j,k,n;
		Scanner in=new Scanner(System.in);
		while(in.hasNextInt())//the end of file
		{
			n=in.nextInt();
			for(i=0;i<=n;i++)
			{
				a1[i]=1;
				a2[i]=0;
			}
			for(i=2;i<=n;i++)
			{
				for(j=0;j<=n;j++)
				{
					for(k=0;k+j<=n;k+=i)
						a2[k+j]=a2[k+j]+a1[j];
				}
				for(j=0;j<=n;j++)
				{
					a1[j]=a2[j];
					a2[j]=0;
				}
			}
			System.out.println(a1[n]);
		}
	}
}
HDU1085
给出1分2分5分硬币的个数,问不能表示的最小整数是多少,这个题由于每个分值的个数有限,所以必须修改源程序,使得个数作为结束的限制条件。
源码如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
public class Main {
	public static final int N=8001;
	public static void main(String args[])
	{
		int a1[]=new int[N],a2[]=new int[N],i,j,k,n,num[]=new int[3],max,tmp,t,nn;
		Scanner in=new Scanner(System.in);
		boolean flag=true;
		while(flag)
		{
			num[0]=in.nextInt();
			num[1]=in.nextInt();
			num[2]=in.nextInt();
			if((num[0]+num[1]+num[2])<1)
				break;
			max=1*num[0]+2*num[1]+5*num[2];//这个个数的硬币可以表示的最大数
			for(i=0;i<=max;i++)
			{
				a1[i]=0;
				a2[i]=0;
			}
			for(i=0;i<=num[0];i++)
				a1[i]=1;
			for(i=2;i<=3;i++)
			{
				if(2 == i)
				{
					t=2;
					nn=num[0];
				}
				else
				{
					t=5;
					nn=num[0]+2*num[1];
				}
				for(j=0;j<=nn;j++) //目前已经到达的最高次幂
				{
					tmp=0;
					for(k=0;k<=num[i-1];++k)//与当前相乘的项的后面括号中的项数
					{
						a2[j+tmp]=a2[j+tmp]+a1[j];
						tmp+=t;
					}
				}
				for(j=0;j<=max;j++)
				{
					a1[j]=a2[j];
					a2[j]=0;
				}
			}
			for(i=1;i<=max;i++)
			{
				if(0 == a1[i])
				{
					System.out.println(i);
					break;
				}
			}
			if(i>max) //从1-最大值都可以表示,那么就输出可以表示的最大值+1
				System.out.println(max+1);
		}
	}
}

HDU1085
给出1分2分5分硬币的个数,问不能表示的最小整数是多少,这个题由于每个分值的个数有限,所以必须修改源程序,使得个数作为结束的限制条件。
源码如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
public class Main {
	public static final int N=8001;
	public static void main(String args[])
	{
		int a1[]=new int[N],a2[]=new int[N],i,j,k,n,num[]=new int[3],max,tmp,t,nn;
		Scanner in=new Scanner(System.in);
		boolean flag=true;
		while(flag)
		{
			num[0]=in.nextInt();
			num[1]=in.nextInt();
			num[2]=in.nextInt();
			if((num[0]+num[1]+num[2])<1)
				break;
			max=1*num[0]+2*num[1]+5*num[2];//这个个数的硬币可以表示的最大数
			for(i=0;i<=max;i++)
			{
				a1[i]=0;
				a2[i]=0;
			}
			for(i=0;i<=num[0];i++)
				a1[i]=1;
			for(i=2;i<=3;i++)
			{
				if(2 == i)
				{
					t=2;
					nn=num[0];
				}
				else
				{
					t=5;
					nn=num[0]+2*num[1];
				}
				for(j=0;j<=nn;j++) //目前已经到达的最高次幂
				{
					tmp=0;
					for(k=0;k<=num[i-1];++k)//与当前相乘的项的后面括号中的项数
					{
						a2[j+tmp]=a2[j+tmp]+a1[j];
						tmp+=t;
					}
				}
				for(j=0;j<=max;j++)
				{
					a1[j]=a2[j];
					a2[j]=0;
				}
			}
			for(i=1;i<=max;i++)
			{
				if(0 == a1[i])
				{
					System.out.println(i);
					break;
				}
			}
			if(i>max) //从1-最大值都可以表示,那么就输出可以表示的最大值+1
				System.out.println(max+1);
		}
	}
}

hdu1171
题目大意:给出n件物品,然后是每件物品的价值及数量,现在想把这些物品分成两份,保证两份的价值尽可能的相等。举例:
输入:
3
10 1
20 1
30 1
那么每一份可以达到20。
输入:
3
5 1
10 2
16 1
那么我们现在只能分为21 20,是两部分尽可能的相等。
现在我们利用母函数来做这个题目,将价值看成x的指数就可以了,首先观察下数的范围n*v*w=25000,我们开一个25000大小的数组,按照常规思路将母函数展开。
然后看x的指数能达到的所有值,从中间开始判断这个次幂的系数是否为0,这里有个技巧就是我们不用计算到最大的价值(total=v[0]*w[0]+...v[n-1]*w[n-1]),
因为我们的目的就是计算出来两两分开后的价值,只需要计算total/2之前的可以达到的价值就可以了,用totoal减去这个最大数就是剩余分给第二个部门的价值了。

源码如下:

import java.util.Scanner;

public class Main{
	public static final int N=51;
	public static final int SIZE=250001;
	public static void main(String args[])
	{
		int n,i,j,tmp,sum=0,total=0,k;
		int v[]=new int [N],w[]=new int [N];
		int a1[]=new int[SIZE],a2[]=new int[SIZE];
		Scanner in=new Scanner(System.in);
		while((n=in.nextInt())>0)
		{
			total=0;
			for(i=0;i<n;i++)
			{
				v[i]=in.nextInt();
				w[i]=in.nextInt();
				total+=v[i]*w[i];
			}
		//	System.out.println(total);
			sum=total/2;
			for(i=0;i<=sum;i++)
			{
				a1[i]=0;
				a2[i]=0;
			}
			tmp=0;
			for(i=0;i<=w[0];i++)
			{
				a1[tmp]=1;
				tmp=tmp+v[0];
			}
			for(i=1;i<n;i++)
			{
				for(k=0;k<=sum;k++)
				{
					tmp=0;
					for(j=0;j<=w[i] && (k+tmp)<=sum;j++)
					{
						a2[k+tmp]=a2[k+tmp]+a1[k];
						tmp+=v[i];
					}
				}
				for(k=0;k<=sum;k++)
				{
					a1[k]=a2[k];
					a2[k]=0;
				}
			}
			for(i=sum;i>=0;i--)
			{
				if(a1[i] != 0)
				{
					System.out.print(total-i);
					System.out.print(" ");
					System.out.println(i);
					break;
				}
			}
		}
	}
}

hdu1709
题目说给出n个重量,问不能称出的重量是多少?我们利用母函数求出能称出的,称不出的自然就出来的,这个题和前几个不同的是砝码可以两边放,
即如果我们现在有1 2 9 的砝码,那么左边放置1和2,右边放置9,那么我们可以称出来重量为6,这个可以根据求出来的大的重量减去求出来的小的重量,
就得到两边的差值了。

#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int BASE=10100;
const int N=110;
int a1[BASE],a2[BASE],ans[BASE],a[N];
int main()
{
	int i,n,j,sum,k;
	while(scanf("%d",&n) != EOF)
	{
		sum=0;
		for(i=0;i<n;i++)
		{
			scanf("%d",&a[i]);
			sum+=a[i];
		}
		memset(a1,0,sizeof(a1));
		memset(a2,0,sizeof(a2));
		a1[0]=1;
		a1[a[0]]=1;
		for(i=1;i<n;i++)
		{
			for(j=0;j<=sum;j++)
			{
				for(k=0;k+j<=sum && k<=a[i];k+=a[i])
					a2[k+j]+=a1[j];
			}
			for(j=0;j<=sum;++j)
			{
				a1[j]=a2[j];
				a2[j]=0;
			}
		}
		int cnt=0;
		for(i=sum;i>=2;i--)
		{
			if(a1[i]==0)
				continue;
			for(j=1;j<i;j++) //得到两边的差
			{
				if(a1[j]==0)
					continue;
				a2[i-j]=1;
			}
		}
		for(i=1;i<=sum;i++)
		{
			if(a1[i]==0 && a2[i]==0)
			{
				ans[cnt++]=i;
			}
		}
		printf("%d\n",cnt);
		for(i=0;i<cnt;i++)
			printf(i<cnt-1?"%d ":"%d\n",ans[i]); 
	}
	return 0;
}

hdu2069
题目给出一个coin,求出所有的组合方式,和整数拆分差不多,但是这个题目有个限制条件就是你所有的硬币总数不能大于100,这个题没想出来怎么用
母函数,直接dfs过的,时间上肯定会长一点因为要递归吗,但是问题解决了就不去想别的方法了,懒人。。。。。

#include <iostream>
#include <stdio.h>
using namespace std;
const int N=251;
int cnt=0,c[5]={1,5,10,25,50};
void fun(int idx,int curSum,int sum,int num)
{
	if(curSum==sum && num<=100)
	{
		cnt++;
		return;
	}
	for(int i=idx;i<=4;i++)
	{
		if(curSum+c[i]<=sum && (1+num)<=100)
			fun(i,curSum+c[i],sum,1+num);
	}
}
int main()
{
	int sum;
	while(scanf("%d",&sum)!=EOF)
	{
		cnt=0;
		fun(0,0,sum,0);
		cout<<cnt<<endl;
	}
	return 0;
}


hdu2152
给出N种水果,及每种水果必须买的数量限制,必须至少买A个,至多买B个,然后给出一个数量M,问所有的组合方案数,这个利用母函数模板稍微修改下就可以了。
对于至少的处理方案就是我们最开始的时候就将所有至少买的个数相加,这样得出至少买的总数量与M比较,如果大于M,说明无法满足组合数为M的方案
(因为最小的总数都>M),这样我们求出来最小的之后,用至多的减去这个至少的,就是我们还可以利用的数量,这样就转化为了一般的有个数限制的母函数了。
代码:

#include <iostream>
#include <stdio.h>
using namespace std;
const int N=10010;
int main()
{
	int i,j,k,sum,a1[N],a2[N],less[N],more[N],n,m;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		sum=0;
		for(i=0;i<n;i++)
		{
			scanf("%d%d",&less[i],&more[i]);
			sum+=less[i];
			more[i]-=less[i];
		}
		if(sum>m)
		{
			printf("0\n");
			continue;
		}
		for(i=0;i<=m;i++)
		{
			a1[i]=0;
			a2[i]=0;
		}
		for(i=0;i<=more[0];i++)
			a1[i+sum]=1;
		for(i=1;i<n;i++)
		{
			for(j=0;j<=m;j++)
			{
				for(k=0;k<=more[i] && (k+j)<=m;++k)
				{
					a2[j+k]+=a1[j];
				}
			}
			for(j=0;j<=m;j++)
			{
				a1[j]=a2[j];
				a2[j]=0;
			}
		}
		cout<<a1[m]<<endl;
	}
	return 0;
}

posted on 2011-10-26 09:58  buptLizer  阅读(631)  评论(0编辑  收藏  举报

导航