生成函数(母函数)

本文深入探讨了生成函数在解决硬币兑换、整数拆分、组合选取等问题中的应用,并通过具体实例展示了如何利用生成函数解决HDU在线判题系统中的相关题目,如SquareCoins、Ignatius and the Princess III、Holding Bin-Laden Captive!等。此外,还分析了在实现算法时的优化技巧和避免超时的方法。
摘要由CSDN通过智能技术生成

目录

一,生成函数

1,普通型生成函数

2,指数型生成函数

3,狄利克雷生成函数

二,生成函数的应用

1,硬币兑换问题

2,整数拆分问题

3,组合选取问题

4,骰子趣题

三,OJ实战

HDU 1398 Square Coins

HDU 1028 Ignatius and the Princess III

HDU 1085 Holding Bin-Laden Captive!

HDU 1171 Big Event in HDU(0-1背包)

HDU 1709 The Balance

HDU 2152 Fruit

OpenJ_Bailian 4120 硬币


一,生成函数

生成函数也叫母函数,是一种通过数列生成函数,研究函数从而得到数列本身的性质的一种方法。

按照函数形式,分为普通型生成函数、指数型生成函数、狄利克雷生成函数

1,普通型生成函数

用于解决组合问题

2,指数型生成函数

用于解决排列问题

3,狄利克雷生成函数

 

 

 

 

二,生成函数的应用

1,硬币兑换问题

2,整数拆分问题

3,组合选取问题

4,骰子趣题

问题:

2个点数不正常的骰子,都满足每个面上的点数是正整数(不同的面上可以有相同的点数),而且这2个骰子可以是不同的,独立地随机产生6个面上的点数中的一个。(比如说,如果2个骰子是1 2 3 6 6 8和2 3 3 4 4 4 ,那么和为10的概率就是7/36)神奇的是,2个骰子的点数之和也是2-12,而且出现的概率和点数正常的2个骰子出现2-12的概率对应相等!
那么这2个不正常的骰子点数到底是多少呢?(本题有唯一解)

解法:

求2个正整数系数的多项式,使得他们的乘积恒等于(x+x^2+x^3+x^4+x^5+x^6)^2 ,而且这2个多项式的系数和都是6(因为有6个面),同时,因为题目要求每个面的点数都是正的,所以2个多项式都没有常数项。
变成这样,这个问题就非常简单了。
设这2个多项式是f 和 g ,则f * g =(x+x^2+x^3+x^4+x^5+x^6)^2=x * (x + 1) * (x^2 - x + 1) * (x^2 + x + 1)*x * (x + 1) * (x^2 - x + 1) * (x^2 + x + 1)
根据多项式的唯一分解定理,f 和 g 就是把 (x)    (x + 1)   (x^2 - x + 1)    (x^2 + x + 1)   (x)  (x + 1)  (x^2 - x + 1)   (x^2 + x + 1) 这8个多项式划分而已。
因为f 和 g没有常数项,所以f 和 g 各含一个(x)
因为f的系数和是f(1),所以f(1)=6,同理g(1)=6,而(x)    (x + 1)   (x^2 - x + 1)    (x^2 + x + 1)这4个多项式在x=1处的值分别为 1 2 1 3 ,所以f  包含 (x + 1)和(x^2 + x + 1),g 也包含 (x + 1)和(x^2 + x + 1)
这就是生成函数的神奇之处!
因为是2个不同的骰子,所以 f 和 g 只能一个是x * (x + 1)  * (x^2 + x + 1),另一个是x * (x + 1) * (x^2 - x + 1) * (x^2 + x + 1)* (x^2 - x + 1),即x^4 + 2 x^3 + 2 x^2 +  x 和  x^8 +  x^6 +  x^5 +  x^4 +  x^3 +  x
即1个骰子是4 3 3 2 2 1 ,另外一个是8 6 5 4 3 1
ps: 433221可以看成321,432,就是把原来的654变成了432
865431可以看成864,531,就是把原来的642变成了864
至于这到底怎么解释,我记得我当年是理解的,现在已经不记得了。。。

三,OJ实战

HDU 1398 Square Coins

题目:
Description

People in Silverland use square coins. Not only they have square shapes but also their values are square numbers. Coins with values of all square numbers up to 289 (=17^2), i.e., 1-credit coins, 4-credit coins, 9-credit coins, ..., and 289-credit coins, are available in Silverland. 
There are four combinations of coins to pay ten credits: 

ten 1-credit coins, 
one 4-credit coin and six 1-credit coins, 
two 4-credit coins and two 1-credit coins, and 
one 9-credit coin and one 1-credit coin. 

Your mission is to count the number of ways to pay a given amount using coins of Silverland. 
Input

The input consists of lines each containing an integer meaning an amount to be paid, followed by a line containing a zero. You may assume that all the amounts are positive and less than 300. 
Output

For each of the given amount, one line containing a single integer representing the number of combinations of coins should be output. No other characters should appear in the output. 
Sample Input

2
10
30
0
Sample Output

1
4
27

本题的母函数:

假设g(i,x)=1+t+t^2+t^3......其中t=x^(i*i)

再设f(x)=g(1,x)*g(2,x)*g(3,x)......

对于输入的整数n,f(x)中x^n的系数即为要输出的答案。

代码:

#include<iostream>
using namespace std;

int main()
{
	int s[301], n;
	for (int i = 0; i < 301; i++)s[i] = (i == 0);
	for (int k = 1; k < 18; k++)for (int i = 300; i>0; i--)
	for (int j = k*k; j <= i; j += k*k)s[i] += s[i - j];
	while (cin >> n)
	{
		if (n == 0)break;
		cout << s[n] << endl;
	}
	return 0;
}

HDU 1028 Ignatius and the Princess III

题目:


Description

"Well, it seems the first problem is too easy. I will let you know how foolish you are later." feng5166 says. 

"The second problem is, given an positive integer N, we define an equation like this: 
  N=a[1]+a[2]+a[3]+...+a[m]; 
  a[i]>0,1<=m<=N; 
My question is how many different equations you can find for a given N. 
For example, assume N is 4, we can find: 
  4 = 4; 
  4 = 3 + 1; 
  4 = 2 + 2; 
  4 = 2 + 1 + 1; 
  4 = 1 + 1 + 1 + 1; 
so the result is 5 when N is 4. Note that "4 = 3 + 1" and "4 = 1 + 3" is the same in this problem. Now, you do it!" 
Input

The input contains several test cases. Each test case contains a positive integer N(1<=N<=120) which is mentioned above. The input is terminated by the end of file. 
Output

For each test case, you have to output a line contains an integer P which indicate the different equations you have found. 
Sample Input

4
10
20
Sample Output

5
42
627

这个题目和HDU - 1398 Square Coins(母函数)其实差不多

代码也差不多,我稍稍一改就得到了本题的代码。

代码:

#include<iostream>
using namespace std;

int main()
{
	int s[121], n;
	for (int i = 0; i <= 120; i++)s[i] = (i == 0);
	for (int k = 1; k <=120; k++)for (int i = 120; i>0; i--)
	for (int j = k; j <= i; j += k)s[i] += s[i - j];
	while (cin >> n)cout << s[n] << endl;
	return 0;
}

HDU 1085 Holding Bin-Laden Captive!

题目:
Description

We all know that Bin-Laden is a notorious terrorist, and he has disappeared for a long time. But recently, it is reported that he hides in Hang Zhou of China! 
“Oh, God! How terrible! ” 
Don’t be so afraid, guys. Although he hides in a cave of Hang Zhou, he dares not to go out. Laden is so bored recent years that he fling himself into some math problems, and he said that if anyone can solve his problem, he will give himself up! 
Ha-ha! Obviously, Laden is too proud of his intelligence! But, what is his problem? 
“Given some Chinese Coins (硬币) (three kinds-- 1, 2, 5), and their number is num_1, num_2 and num_5 respectively, please output the minimum value that you cannot pay with given coins.” 
You, super ACMer, should solve the problem easily, and don’t forget to take $25000000 from Bush! 
Input

Input contains multiple test cases. Each test case contains 3 positive integers num_1, num_2 and num_5 (0<=num_i<=1000). A test case containing 0 0 0 terminates the input and this test case is not to be processed. 
Output

Output the minimum positive value that one cannot pay with given coins, one line for one case. 
Sample Input

1 1 3
0 0 0
Sample Output

4
母函数的思路:

#include<iostream>
using namespace std;

int main()
{
	int s[8001], n1, n2, n5, l[6] = { 0, 1, 2, 3, 4, 5 };
	while (cin >> n1 >> n2 >> n5)
	{
		if (n1 + n2 + n5 == 0)break;
		for (int i = 0; i <= 8000; i++)s[i] = 0;
		for (int i = 0; i <= n1; i++)s[i] = 1;
		for (int i = 8000; i > 0; i--)for (int j = 2; j <= i&&j <= n2 * 2; j += 1)s[i] += s[i - j];
		for (int i = 8000; i > 0; i--)for (int j = 5; j <= i&&j <= n5 * 5; j += 1)s[i] += s[i - j];
		bool flag = true;
		for (int i = 0; i <= 8000 && flag; i++)
		{
			if (s[i] == 0)
			{
				cout << i << endl;
				flag = false;
			}
		}
		if (flag)cout << 8001 << endl;
	}
	return 0;
}

然而超时了。

针对本题的方法:

#include<iostream>
using namespace std;

int f(int n1, int n2, int n5)
{
	if (n1 == 0)return 1;
	int key = n2 * 2 + n1 + 1;
	if (key < 5)return key;
	return n5 * 5 + key;
}

int main()
{
	int n1, n2, n5;
	while (cin >> n1 >> n2 >> n5)
	{
		if (n1 + n2 + n5 == 0)break;
		cout << f(n1, n2, n5) << endl;
	}
	return 0;
}

HDU 1171 Big Event in HDU(0-1背包)

题目:
Description

Nowadays, we all know that Computer College is the biggest department in HDU. But, maybe you don't know that Computer College had ever been split into Computer College and Software College in 2002. 
The splitting is absolutely a big event in HDU! At the same time, it is a trouble thing too. All facilities must go halves. First, all facilities are assessed, and two facilities are thought to be same if they have the same value. It is assumed that there is N (0<N<1000) kinds of facilities (different value, different kinds). 
Input

Input contains multiple test cases. Each test case starts with a number N (0 < N <= 50 -- the total number of different facilities). The next N lines contain an integer V (0<V<=50 --value of facility) and an integer M (0<M<=100 --corresponding number of the facilities) each. You can assume that all V are different. 
A test case starting with a negative integer terminates input and this test case is not to be processed. 
Output

For each case, print one line containing two integers A and B which denote the value of Computer College and Software College will get respectively. A and B should be as equal as possible. At the same time, you should guarantee that A is not less than B. 
Sample Input

2
10 1
20 1
3
10 1 
20 2
30 1
-1
Sample Output

20 10
40 40

这个题目,用母函数的思路做,或者用0-1背包的思路做,方法都是一样的,操作也都是差不多的,时间上没什么差别。

代码:

#include<stdio.h>

int num[51], r[127505], sum;

void f()
{
	for (int i = 0; i <= sum / 2; i++)r[i] = (i == 0);
	for (int i = 1; i <= 50; i++)if (num[i])
	for (int j = sum / 2; j >= 0; j--)if (r[j])
	for (int k = i; k<=i*num[i] && k + j <= sum / 2; k += i)r[k + j] += r[j];
	int k = 0;
	for (int i = sum / 2; i >= 0; i--)if (r[i])
	{
		k = i;
		break;
	}
	printf("%d %d\n", sum - k, k);
}

int main()
{
	int n, v, m;
	while (scanf("%d",&n))
	{
		if (n <= 0)break;
		for (int i = 0; i <= 50; i++)num[i] = 0;
		sum = 0;
		while (n--)
		{
			scanf("%d%d", &v, &m);
			num[v] = m;
			sum += v*m;
		}
		f();
	}
	return 0;
}

这个代码AC了,没什么问题。

之前少了一句k<=i*num[i],就是f函数里面的3层循环的最内层的循环,k的终止条件少了这一个。

这样导致结果自然是错误的,但是在OJ里面的结果是超时。

想了半天不知道为什么超时,测试了一下:

输入:

10
50 100
49 100
48 100
47 100
46 100
45 100
44 100
43 100
42 100
41 100

计算的时间比较长,大约就是1秒。

然后仔细检查代码,才发现是少了k<=i*num[i]。

因为少了这一句,所以在时间上会多很多。

其实很多题目都是这样,因为忽略了一个细节,导致计算错误不说,计算时间还多了很多很多。

HDU 1709 The Balance

题目:
Description

Now you are asked to measure a dose of medicine with a balance and a number of weights. Certainly it is not always achievable. So you should find out the qualities which cannot be measured from the range [1,S]. S is the total quality of all the weights. 
Input

The input consists of multiple test cases, and each case begins with a single positive integer N (1<=N<=100) on a line by itself indicating the number of weights you have. Followed by N integers Ai (1<=i<=N), indicating the quality of each weight where 1<=Ai<=100. 
Output

For each input set, you should first print a line specifying the number of qualities which cannot be measured. Then print another line which consists all the irrealizable qualities if the number is not zero. 
Sample Input

3
1 2 4
3
9 2 1
Sample Output

0
2
4 5
 

#include<stdio.h>

int num[20001];

int main()
{
	int n, sum, k, r;
	for (int i = 0; i < 20001; i++)num[i] = 0;
	while (scanf("%d",&n)!=EOF)
	{
		sum = 0;
		num[10000] = 1;
		while (n--)
		{
			scanf("%d", &k);
		for (int i = 10000 - sum; i <= 10000 + sum; i++)if (num[i])num[i - k] = 1;
		for (int i = 10000 + sum; i >= 10000 - sum; i--)if (num[i])num[i + k] = 1;
			sum += k;
		}
		r = 0;
		for (int i = 10000; i <= 10000 + sum; i++)r += (num[i] == 0);
		printf("%d\n", r);
		if (r == 0)for (int i = 10000; i <= 10000 + sum; i++)num[i] = 0;
		else for (int i = 10000; i <= 10000 + sum; i++)
		{
			if (num[i])num[i] = 0;
			else
			{
				printf("%d", i - 10000);
				r--;
				printf(r ? " " : "\n");
			}
		}
		for (int i = 10000 - sum; i <= 10000; i++)num[i] = 0;
	}
	return 0;
}

值得注意的是,数组必须开这么大,少一点都不行,因为100个砝码可能都是100,那样的话sum就是10000

HDU 2152 Fruit

Description

转眼到了收获的季节,由于有TT的专业指导,Lele获得了大丰收。特别是水果,Lele一共种了N种水果,有苹果,梨子,香蕉,西瓜……不但味道好吃,样子更是好看。 

于是,很多人们慕名而来,找Lele买水果。 

甚至连大名鼎鼎的HDU ACM总教头 lcy 也来了。lcy抛出一打百元大钞,"我要买由M个水果组成的水果拼盘,不过我有个小小的要求,对于每种水果,个数上我有限制,既不能少于某个特定值,也不能大于某个特定值。而且我不要两份一样的拼盘。你随意搭配,你能组出多少种不同的方案,我就买多少份!" 

现在就请你帮帮Lele,帮他算一算到底能够卖出多少份水果拼盘给lcy了。 

注意,水果是以个为基本单位,不能够再分。对于两种方案,如果各种水果的数目都相同,则认为这两种方案是相同的。 

最终Lele拿了这笔钱,又可以继续他的学业了~ 
Input

本题目包含多组测试,请处理到文件结束(EOF)。 
每组测试第一行包括两个正整数N和M(含义见题目描述,0<N,M<=100) 
接下来有N行水果的信息,每行两个整数A,B(0<=A<=B<=100),表示至少要买该水果A个,至多只能买该水果B个。 
Output

对于每组测试,在一行里输出总共能够卖的方案数。 
题目数据保证这个答案小于10^9 
Sample Input

2 3
1 2
1 2
3 5
0 3
0 3
0 3
Sample Output

2
12
 

#include<stdio.h>

long long num[101];

int main()
{
	int n, m, a, b;
	while (scanf("%d%d", &n, &m) != EOF)
	{
		for (int i = 0; i <= m; i++)num[i] = (i == 0);
		while (n--)
		{
			scanf("%d%d", &a, &b);
			m -= a, b -= a;
			for (int i = m; i >= 0; i--)if (num[i])
			for (int j = 1; j <= b; j++)if (i + j <= m)num[i + j] += num[i];
		}
		printf("%d\n", m >= 0 ? num[m] : 0);
	}
	return 0;
}

OpenJ_Bailian 4120 硬币

题目:


Description

宇航员Bob有一天来到火星上,他有收集硬币的习惯。于是他将火星上所有面值的硬币都收集起来了,一共有n种,每种只有一个:面值分别为a1,a2… an。Bob在机场看到了一个特别喜欢的礼物,想买来送给朋友Alice,这个礼物的价格是X元。Bob很想知道为了买这个礼物他的哪些硬币是必须被使用的,即Bob必须放弃收集好的哪些硬币种类。飞机场不提供找零,只接受恰好X元。

Input

第一行包含两个正整数n和x。(1 <= n <= 200, 1 <= x <= 10000) 
第二行从小到大为n个正整数a1, a2, a3 … an (1 <= ai <= x)
Output

第一行是一个整数,即有多少种硬币是必须被使用的。 
第二行是这些必须使用的硬币的面值(从小到大排列)。
Sample Input

5 18
1 2 3 5 10
Sample Output

2
5 10
Hint

输入数据将保证给定面值的硬币中至少有一种组合能恰好能够支付X元。 
如果不存在必须被使用的硬币,则第一行输出0,第二行输出空行。

思路:

这个题目,我的想法是,利用一个函数,单独的判断每个硬币是否是必须的。

即去掉一个硬币,判断余下的硬币能不能组成x

假设有3个硬币,是s1,s2,s3,那么母函数就是(1+x^s1)(1+x^s2)(1+x^s3)

判断x^X的系数是否为0即可

得到母函数的时间是O(n*x),所以整个算法是时间是O(n*n*x),那就超时了

实际上,我的第一个算法正是如此。

如何变成O(n*x)的算法呢?

注意到,判断每个硬币是否是必须的这个问题,其实并不是互相独立的。

比如取第2,3,4,5个硬币和取第1,3,4,5个硬币,它们是有很大的共同点的,有大部分的计算都是重复的。

注意到这一点,我们就可以对母函数进行改进了。

首先根据所有的硬币得到母函数f,然后单独判读每个硬币是否是独立的

判断的方法是:对于硬币s1,判断f/(1+x^s1)中x^X的系数是否为0

注意,判断f/(1+x^s1)中x^X的系数是否为0和判断f能否整除1+x^s1完全是两码事

比如说,判断(a0+a1*x+a2*x^2+a3*x^3+a4*x^4+a5*x^5)/(1+x^2)中x^5的系数是否为0,

其实就是判断a1*x+a3*x^3+a5*x^5能否被(1+x^2)整除(这一点实在很巧妙)

这里判断每个硬币是否必须的时候,时间只需要O(x/s1)

所以整个算法是时间是O(n*x)

代码:
 

#include <iostream>
using namespace std;
 
int n, x, d[200], s[10001], t[10001];
bool needs[200];
 
bool need(int k)
{
	for (int i = 0; i <= x; i++)t[i] = s[i];
	int p = x;
	while (p >= d[k])
	{
		t[p - d[k]] -= t[p];
		p -= d[k];
	}
	return t[p] == 0;
}
 
int main()
{
	int sum = 0;
	cin >> n >> x;
	for (int i = 0; i < n; i++)cin >> d[i];
	for (int i = 0; i <= x; i++)s[i] = (i == 0);
	for (int i = 0; i < n; i++)
		for (int j = x - d[i]; j >= 0; j--)s[j + d[i]] += s[j];
	for (int i = 0; i < n; i++)
	{
		needs[i] = need(i);
		sum += (needs[i]);
	}
	cout << sum << (sum ? "\n" : "\n\n");
	for (int i = 0; i < n; i++)if (needs[i])
		cout << d[i] << ((--sum>0) ? " " : "\n");
	return 0;
}

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值