【生成函数解析】
由于CSDN更新过好几个版本,导致一些图片的丢失,但一点都不影响解析生成函数。
利用D题的选课时间作为模版。
给定m个物品的价格和数量,构成价值为n的方案数
1 #include
2 #include
3
4 using namespacestd;5 const int N = 50;6 intn , m ;7 inta[N] , b[N] , c[N] ;8 intf[N] , tmp[N] ;9 intmain()10 {11 intT ;12 scanf("%d",&T);13 while( T--){14 //初始化
15 for( int i = 0 ; i < N ; i++ ) tmp[i] = f[i] = 0;16 f[0] = 1;17 scanf("%d%d",&n,&m);18
19 int Highest_Pow = 0, Limit;20 //枚举物品
21 for( int i = 1 ; i <= m ; i++){22 scanf("%d%d",&a[i],&b[i]);23
24 Limit =min( Highest_Pow , n );25
26 //枚举第一个括号中的次幂
27 for( int j = 0 ; j <= Limit ; j++){28 //枚举第二个括号中的个数,通过它的价值a[i],枚举每一项指数 应为k*a[i]
29 for( int k = 0 ; j + k * a[i] <= n && k <= b[i] ; k++){30 tmp[ j + k*a[i] ] +=f[j];31 }32 }33
34 //更新最高次幂
35 Highest_Pow += a[i] *b[i] ;36 Limit =min( Highest_Pow , n );37 for( int j = 0 ; j <= Limit ; j++){38 f[j] =tmp[j] ;39 tmp[j] = 0;40 }41 }42 printf("%d\n",f[n]);43 }44 return 0;45 }
模版1
1 #include
2 #include
3
4 using namespacestd;5 const int N = 50;6 intn , m ;7 inta[N] , b[N] , c[N] ;8 intf[N] , tmp[N] ;9 intmain()10 {11 intT ;12 scanf("%d",&T);13 while( T--){14 //初始化
15 for( int i = 0 ; i < N ; i++ ) tmp[i] = f[i] = 0;16 f[0] = 1;17 scanf("%d%d",&n,&m);18
19 int Highest_Pow = 0, Limit;20 //枚举物品
21 for( int i = 1 ; i <= m ; i++){22 scanf("%d%d",&a[i],&b[i]);23
24 Limit =min( Highest_Pow , n );25
26 //枚举第一个括号中的次幂
27 for( int j = 0 ; j <= Limit ; j++){28 //枚举第二个括号中的个数,通过它的价值a[i],枚举每一项指数 应为k*a[i]
29 for( int k = 1 ; j + k * a[i] <= n && k <= b[i] ; k++){30 tmp[ j + k*a[i] ] +=f[j];31 }32 }33
34 //更新最高次幂
35 Highest_Pow += a[i] *b[i] ;36 Limit =min( Highest_Pow , n );37 for( int j = 0 ; j <= Limit ; j++){38 f[j] +=tmp[j] ;39 tmp[j] = 0;40 }41 }42 printf("%d\n",f[n]);43 }44 return 0;45 }
模版2
A - Holding Bin-Laden Captive!
【题目来源】HDU - 1085
【题意】
给定n1,n2,n3分别代表硬币面值为1,2,5元的数量
请输出由这些硬币 凑不成的最小正整数?
例如:
有1个面值为1元的硬币。
有1个面值为2元的硬币。
有3个面值为5元的硬币。
即可构成:{1,2,3,5,6,7,8,10,11,12,13,15,16,17,18}
集合中凑不成的最小正整数为4.
【题解】
基本上就是母函数(生成函数)的模板题
根据题目的样例构造出
( x^0 + x^1 ) * ( x^0 + x^2 + x^4 ) * ( x^0 + x^5 + x^10 + x^15)
请大家用草稿纸手动算一下上述式子。
你就会发现能凑出来的数字会是拥有指数为{1,2,3,5,6,7,8,10,11,12,13,15,16,17,18}的多项式。而我们想要找的答案只需要遍历哪个指数未出现过即可。
这种题型被称为:普通型的母函数(生成函数)
其特点为:给定 ”面值” 和 “数量” 问其“构成的方案数”或“能否构成”。
而这个题目是:固定面值,给出数量,问起能否构成。
算法复杂度O(n^3)
计算过程就是模拟多项式乘法,()*()两个多项式之间进行乘法运算。
1 #include
2 const int N = 8e3 + 10;3 int weight[3] = { 1 , 2 , 5};4 int num[3] ;5 intf[N] ;6 inttmp[N] ;7 intmain()8 {9 while( scanf("%d%d%d",&num[0],&num[1],&num[2]) , (num[0]+num[1]+num[2]) ){10 //初始化
11 f[0] = 1;12 for( int i = 1 ; i < N ; i++ ) f[i] = 0;13
14 //枚举第一个括号
15 for( int i = 1 ; i <= num[0] ; i++ ) f[i] = 1;16
17 int Highest_Pow = 0;18
19 //第一层枚举 硬币种类
20 for( int i = 0 ; i < 3 ; i ++){21 //第二层枚举 第一个括号中 从0次幂到最高次幂
22 for( int j = 0 ; j <= Highest_Pow ; j ++){23 //第三层枚举 第二个括号中的数量
24 for( int k = 1 ; k <= num[i] ; k++){25 f[ j + k * weight[i] ] |=f[j];26 }27 }28 //更新最高次幂
29 Highest_Pow += weight[i] *num[i] ;30 }31
32 for( int i = 1 ; i < N ; i++){33 if( !f[i] ){34 printf("%d\n",i);35 break;36 }37 }38 }39 return 0;40 }
Holding Bin-Laden Captive!-母函数
扩展做法-多重背包
我们习惯做的是01背包问题,多重背包仅仅是多增加了一个维度:"物品数量"任何"普通型母函数问题" 都可以转化成 "多重背包问题"多重背包模型会比母函数更加好写。
问题转化是一样的,都是根据生成函数的特点而设计。
仅仅是计算的角度不一样。
同时,多重背包灵活性体现在复杂度
朴素做法 O(N^3) -> 二进制优化 O(N * V *logN ) -> 单调队列优化 O(N *V)
1 //扩展(多重背包做法-朴素做法)
2 #include
3 const int N = 8e3 + 20;4
5 intf[N];6 int num[3];7 int weight[3] = { 1 , 2 , 5};8
9 intmain()10 {11 while( scanf("%d%d%d",&num[0],&num[1],&num[2]) , ( num[0] + num[1] + num[2] ) ){12 f[0] = 1;13 for( int i = 1 ; i < N ; i++ ) f[i] = 0;14
15 int V = num[0] + num[1] * 2 + num[2] * 5;16 //枚举物品个数
17 for( int i = 0 ; i < 3 ; i ++){18 //枚举每个物品的重量
19 for( int j = V ; j >= weight[i]; j --){20 //在j容量限制下枚举物品个数,同时进行01背包处理
21 for( int k = 1 ; k <= num[i] ; k++){22 if( j - k * weight[i] >= 0){23 f[j] |= f[j - k *weight[i]] ;24 }25 }26 }27 }28
29 for( int i = 1 ; i < N ; i++){30 if( !f[i] ){31 printf("%d\n",i);32 break;33 }34 }35 }36 return 0;37 }
扩展(多重背包做法-朴素做法)
B - Big Event in HDU
【题目来源】HDU - 1171
【题意】
给定n个物品的“价值”,“数量”
请问如果尽可能划分出两堆物品 ,要求 两堆物品间价值之差 尽可能小。
例如:
有1个物品价值为10
有2个物品价值为20
有1个物品价值为30
通过划分两堆物品 {10,30} = 40 ,{20,20} = 40所以答案为40 40划分两堆物品如果价值不同,请先输出价值大的一堆,后输出价值小的一堆
【生成函数做法】
和上一个题目类似
给定物品的价值和数量,问是否存在尽可能靠近total/2的值。
首先处理好输入,初始化工作。
然后进行生成函数模板代入。
最后遍历,找出尽可能靠近total/2的情况
1 #include
2 const int N = 5e6 + 10;3 const int M = 1e3 + 10;4 intf[N] ;5 intnum[M] , w[M] ;6 intmain()7 {8 intn , Highest_Pow , total ;9 while( scanf("%d",&n) ,( n>0) ){10 total = 0;11 for( int i = 0 ; i < n ; i ++){12 scanf("%d%d",&w[i] , &num[i] );13 total += w[i] *num[i] ;14 }15 for (int i = 1 ; i <= total / 2 ; i++ ) f[i] = 0;16 f[0] = 1;17 Highest_Pow = 0;18
19 for( int i = 0 ; i < n ; i ++){20 for( int j = 0 ; j <= Highest_Pow ; j ++){21 for( int k = 1 ; k <= num[i] ; k++){22 f[j+k*w[i]] |=f[j] ;23 }24 }25 Highest_Pow += w[i] *num[i] ;26 }27 for( int i = total/2 ; i >= 0 ; i--){28 if( f[i] ){29 printf("%d %d\n",total-i,i);30 break;31 }32 }33 }34 return 0;35 }
Big Event in HDU-母函数做法
【01背包做法】
经典的背包问题,01背包
首先限制背包容量为总容量的一半。
然后把所有的物品离散化,同时进行01背包处理后。
答案为: {total- f[V] , f[V]}
1 #include
2 #include
3 using namespacestd;4
5 const int N = 5e6 + 10;6 const int M = 1e3 + 10;7 intf[N];8 intw[M];9 intnum[M];10 intmain()11 {12 intn , V , total ;13 while( scanf("%d",&n) , ( n > 0) ){14
15 total = V = 0;16
17 for( int i = 0 ; i < n ; i++){18 scanf("%d%d",&w[i] , &num[i] );19 total += w[i] *num[i] ;20 }21 V = total / 2;22
23 for( int i = 0 ; i <= V ; i ++ ) f[i] = 0;24
25 for( int i = 0 ; i < n ; i++){26 while( num[i]--){27 for( int j = V ; j >= w[i] ; j--){28 f[j] = max( f[j] , f[j-w[i]] +w[i] );29 }30 }31 }32
33 printf("%d %d\n",total -f[V] , f[V] );34 }35 return 0;36 }
Big Event in HDU-01背包做法
C - Square Coins
【题目来源】HDU - 1398
【题意】
给定价值为1,4,9……289(17^2)的硬币
给定一个值,请问有多少种构造的方法?
例如:10 = 1 + 1 …… + 1
10 = 1 + 1 + 1 + 1 + 1 + 1 + 4
10 = 1 + 1 + 4 + 4
10 = 1 + 9答案就是4
【生成函数做法】
这个题目和课堂上讲的例题一模一样,仅仅是面值发生改变,其余的都没有变。
该题的普通母函数的结构为"面值固定","数量不定",求解构成某个数值的方案数?
1 #include
2 using namespacestd;3 const int M = 305;4 const int N = 20;5 intf[M];6 intv[N];7 inttmp[M];8 voidInit(){9 for( int i = 0 ; i < 17 ; i++){10 v[i] = ( i+1 ) * ( i+1);11 }12 f[0] = 1;13 for( int i = 0 ; i < 17 ; i ++){14 for( int j = 0 ; j <= 300 - v[i] ; j++){15 for( int k = 1 ; j + k*v[i] < M ; k++){16 tmp[ j+k*v[i] ] +=f[j] ;17 }18 }19 for(int j = 1 ; j <= 300 ; j++){20 f[j] +=tmp[j] ;21 tmp[j] = 0;22 }23 }24 }25 intmain(){26
27 intn ;28 Init();29 while( scanf("%d",&n) , n > 0){30 printf("%d\n",f[n]);31 }32 return 0;33 }
Square Coins-母函数
【完全背包做法】
完全背包 指的是 “物品数量无限”的背包问题
这个题目其实就是和之前生成函数有所区别在于个数不再受限。
所以问题可以转变成可取无限多次的背包问题->"完全背包"
1 #include
2 using namespacestd;3 const int M = 305;4 const int N = 20;5 intf[M];6 intv[N];7 inttmp[M];8 voidInit(){9 for( int i = 0 ; i < 17 ; i++){10 v[i] = ( i+1 ) * ( i+1);11 }12 f[0] = 1;13 for( int i = 0 ; i < 17 ; i ++){14 for( int j = v[i] ; j <= 300 ; j++){15 f[j] += f[j-v[i]];16 }17 }18 }19 intmain(){20
21 intn ;22 Init();23 while( scanf("%d",&n) , n > 0){24 printf("%d\n",f[n]);25 }26 return 0;27 }
Square Coins-完全背包做法
D - 选课时间
题目链接:HDU - 2079
【题意】
给定n门课,每一门课都有相应的学分和数目
请问构成学分为m的方案有多少种?
【母函数做法】
和课堂上例题"整数划分问题"一模一样。
给定“学分”和“数量” 求解某学分的方案数
1 #include
2 using namespacestd;3 const int N = 50;4 intn , m ;5 inta[N] , b[N] , c[N] ;6 intf[N] , tmp[N] ;7 intmain()8 {9 intT ;10 scanf("%d",&T);11 while( T--){12
13 for( int i = 0 ; i < N ; i++ ) tmp[i] = f[i] = 0;14 f[0] = 1;15
16 scanf("%d%d",&n,&m);17
18 for( int i = 1 ; i <= m ; i++){19 scanf("%d%d",&a[i],&b[i]);20 for( int j = 0 ; j <= n ; j++){21 for( int k = 1 ; j + k * a[i] <= n && k <= b[i] ; k++){22 tmp[ j + k*a[i] ] +=f[j];23 }24 }25 for( int j = 0 ; j <= n ; j++){26 f[j] +=tmp[j] ;27 tmp[j] = 0;28 }29 }30 printf("%d\n",f[n]);31 }32 return 0;33 }
选课时间-母函数
【多重背包做法】
给定n个物品,给定物品的“价值”和”数量“
请问构成价值为n的方案数有多少?
1 #include
2 using namespacestd;3 const int N = 50;4 intf[N] ;5 inta[N] , b[N] ;6 intn , m;7 intmain()8 {9 intT;10 scanf("%d",&T);11 while(T--){12 scanf("%d%d",&n,&m);13 for( int i = 0 ; i < m ; i++){14 scanf("%d%d",&a[i],&b[i]);15 }16
17 for( int i = 1 ; i <= n ; i ++ ) f[i] = 0;18 f[0] = 1;19 for( int i = 0 ; i < m ; i ++){20 for( int j = n ; j >= a[i] ; j--){21 for( int k = 1 ; k <= b[i] && j - k*a[i] >= 0 ; k++){22 f[j] += f[j-k*a[i]];23 }24 }25 }26 printf("%d\n",f[n]);27 }28 return 0;29 }
选课时间-多重背包
E - 找单词
题目来源:HDU - 2082
【题意】
给定26个字母的数量,每个字母的权值 按顺序排。
A- 1 , B - 2 …… Z - 26请问由这些数量字母组成的单词价值<=50 的方案数?
【普通型生成函数做法】
根据题目所给定的数量进行构造生成函数。
然后模拟多项式乘法,累加系数[1,50]的方案数。
1 #include
2 #include
3 #include
4 using namespacestd;5 const int N = 30;6 const int M = 50;7 int f[M+10] ;8 int tmp[M+10] ;9 intnum[N];10 intmain()11 {12 intT ;13 scanf("%d",&T);14 while( T--){15 for( int i = 1 ; i <= 26 ; i++ ) scanf("%d",&num[i]);16 for( int i = 0 ; i <= M ; i++ ) f[i] = 0;17 f[0] = 1;18 int Highest_Pow = 0, Limit ;19 for( int i = 1 ; i <= 26 ; i++){20 Limit =min( Highest_Pow , M );21 for( int j = 0 ; j <= Limit ; j++){22 for ( int k = 1 ; k <= num[i] && j + k * i <= M ; k ++){23 tmp[j+k*i] +=f[j];24 }25 }26 Highest_Pow += num[i] *i ;27 Limit =min( Highest_Pow , M );28 for( int j = 0 ; j <= Limit ; j ++){29 f[j] +=tmp[j];30 tmp[j] = 0;31 }32 }33 int ans = 0;34 for( int i = 1 ; i <= M ; i++){35 ans +=f[i] ;36 }37 printf("%d\n",ans);38 }39 return 0;40 }
找单词-母函数
【多重背包做法】
给定物品为数量,价值固定,求出价值之和<=50的方案数
1 #include
2 #include
3 using namespacestd;4 const int N = 55;5 const int M = 50;6 intf[N],num[N];7
8 intmain()9 {10 intT;11 scanf("%d",&T);12 while(T--){13 for( int i = 1 ; i <= 26 ; i ++ ) scanf("%d",&num[i]);14 for( int i = 1 ; i <= M ; i ++ ) f[i] = 0;15
16 f[0] = 1;17 for( int i = 1 ; i <= 26 ; i ++){18 for( int j = M ; j >= i ; j--){19 for( int k = 1 ; k <= num[i] && j - k * i >= 0 ; k ++){20 f[j] += f[j-k*i];21 }22 }23 }24
25 int ans = 0;26 for( int i = 1 ; i <= M ; i ++){27 ans +=f[i];28 }29 printf("%d\n",ans);30 }31 return 0;32 }
找单词-多重背包
F - Crisis of HDU
【题目来源】HDU - 2110
【小结】
这个题意很难读懂。
给定n个物品 的价值及数量
请问 价值为总价值的三分之一 的 方案数?
【题解】
给出物品的价格及数量。
首先判断 总价值是否能被3整除。同时计算完后,如果方案数为0还是输出"sorry"。
然后进行套母函数模板或者多重背包的模板即可
【注意】
取模运算
(a+ b) % mod = a % mod + b %mod
所以过程中进行模运算不影响最后答案在mod意义下的结果
1 #include
2 #include
3 using namespacestd;4
5 const int mod = 10000;6 const int N = 1e3+10;7
8 intf[N],a[N],b[N],tmp[N];9 intn ;10
11 intmain()12 {13 while( scanf("%d",&n) , (n != 0) ){14 int total = 0;15 for( int i = 0 ; i < n ; i ++){16 scanf("%d%d",&a[i],&b[i]);17 total += a[i] *b[i] ;18 }19
20 if( total % 3 != 0){21 printf("sorry\n");22 continue;23 }24
25 int Highest_Pow = 0, Limit ;26 for( int i = 1 ; i <= total / 3 ; i ++ ) f[i] = 0;27 f[0] = 1;28 for( int i = 0 ; i < n ; i ++){29 Limit = min( Highest_Pow , total / 3);30 for( int j = 0 ; j <= Limit ; j ++){31 for( int k = 1 ; k <= b[i] && j + k * a[i] <= total / 3 ; k ++){32 tmp[ j + k*a[i] ] = ( tmp[j+k*a[i]] + f[j] ) %mod ;33 }34 }35
36 Highest_Pow += a[i] *b[i] ;37 Limit = min( Highest_Pow , total / 3);38 for( int j = 0 ; j <= Limit ; j++){39 f[j] = (f[j] + tmp[j]) %mod ;40 tmp[j] = 0;41 }42 }43
44 if( f[ total / 3] ){45 printf("%d\n",f[total/3]);46 }else{47 printf("sorry\n");48 }49 }50 return 0;51 }
Crisis of HDU-母函数
1 #include
2 const int N = 1e3 + 10;3 const int mod =1e4 ;4 intf[N] , a[N] , b[N] ;5
6 intmain()7 {8 intn ;9 while( scanf("%d",&n) , n ){10 int sum = 0;11 for( int i = 0 ; i < n ; i++){12 scanf("%d%d",&a[i],&b[i]);13 sum += a[i] *b[i] ;14 }15 for( int i = 1 ; i <= sum / 3 ; i++ ) f[i] = 0;16 f[0] = 1;17
18 for( int i = 0 ; i < n ; i++){19 for( int j = sum / 3 ; j >= a[i] ; j --){20 for( int k = 1 ; k <= b[i] && j - k * a[i] >= 0 ; k++){21 f[j] = ( f[j] + f[j-k*a[i]] ) %mod;22 }23 }24 }25 if( sum % 3 || ( sum % 3 == 0 && f[sum/3] == 0) ){26 printf("sorry\n");27 }else{28 printf("%d\n",f[sum/3]);29 }30 }31 return 0;32 }
Crisis of HDU-多重背包
G - Fruit
【题目来源】HDU - 2152
【题意】
题目意思是说:价值全为1的物品,然后给出n种不同物品,以及对应的数量。
但是这个数量是一个范围的概念[A,B],即物品的数量必须是[A,B]之间
求出组成价值为m的方案数
【题解】
如果这道题用母函数做,只需要修改题目中所固定的数量即可。
但如果用多重背包来做的话,背包问题默认必须是从某个起始点开始,也就是从0开始进行递推累加方案数。但是如果这个题目的话不能直接套用,但是我们可以技巧性地把问题转移到从0开始。
数量从[A,B]->[0,B-A],这样的话对应的m也需要减去各组物品的A.
然后套用模版即可。
1 #include
2 #include
3 using namespacestd;4 const int N = 2e3 + 10;5 intf[N] , tmp[N] ;6 intmain()7 {8 intn , m , A , B ;9 while( scanf("%d%d",&n,&m) !=EOF ){10 memset( f , 0 , sizeoff );11 scanf("%d%d",&A,&B);12 for( int i = A ; i <= B ; i++ ) f[i] = 1;13 for( int i = 1 ; i < n ; i ++){14 scanf("%d%d",&A,&B);15 for( int j = 0 ; j <= m ; j++){16 for( int k = A ; k <= B && j + k <= m ; k ++){17 tmp[j+k] +=f[j];18 }19 }20 for( int j = 0 ; j <= m ; j++){21 f[j] =tmp[j];22 tmp[j] = 0;23 }24 }25 printf("%d\n",f[m]);26 }27 return 0;28 }
Fruit-母函数
1 #include
2 #include
3 const int N = 205;4 intf[N] , A[N] , B[N];5 intmain()6 {7 intn , m ;8 while( scanf("%d%d",&n,&m) !=EOF){9 memset( f , 0 , sizeoff );10 f[0] = 1;11
12 for( int i = 0 ; i < n ; i++){13 scanf("%d%d",&A[i],&B[i]);14 m -=A[i];15 B[i] -=A[i];16 }17
18 for( int i = 0 ; i < n ; i++){19 for( int j = m ; j >= 1 ; j --){20 for( int k = 1 ; k <= B[i] && j - k >= 0 ; k ++){21 f[j] += f[j-k];22 }23 }24 }25 if( m < 0 ) printf("0\n");26 else printf("%d\n",f[m]);27 }28 return 0;29 }
Fruit-多重背包