https://blog.csdn.net/Z_sea/article/details/86529635
由于CSDN更新过好几个版本,导致一些图片的丢失,但一点都不影响解析生成函数。
利用D题的选课时间作为模版。
给定m个物品的价格和数量,构成价值为n的方案数
1 #include<cstdio> 2 #include<algorithm> 3 4 using namespace std; 5 const int N = 50 ; 6 int n , m ; 7 int a[N] , b[N] , c[N] ; 8 int f[N] , tmp[N] ; 9 int main() 10 { 11 int T ; 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 ",f[n]); 43 } 44 return 0; 45 }
1 #include<cstdio> 2 #include<algorithm> 3 4 using namespace std; 5 const int N = 50 ; 6 int n , m ; 7 int a[N] , b[N] , c[N] ; 8 int f[N] , tmp[N] ; 9 int main() 10 { 11 int T ; 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 ",f[n]); 43 } 44 return 0; 45 }
【题意】 给定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<stdio.h> 2 const int N = 8e3 + 10 ; 3 int weight[3] = { 1 , 2 , 5 }; 4 int num[3] ; 5 int f[N] ; 6 int tmp[N] ; 7 int main() 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 ",i); 35 break; 36 } 37 } 38 } 39 return 0; 40 }
扩展做法-多重背包 我们习惯做的是01背包问题,多重背包仅仅是多增加了一个维度:"物品数量" 任何"普通型母函数问题" 都可以转化成 "多重背包问题" 多重背包模型会比母函数更加好写。 问题转化是一样的,都是根据生成函数的特点而设计。 仅仅是计算的角度不一样。 同时,多重背包灵活性体现在复杂度 朴素做法 O(N^3) -> 二进制优化 O(N * V *logN ) -> 单调队列优化 O(N * V)
1 //扩展(多重背包做法-朴素做法) 2 #include<stdio.h> 3 const int N = 8e3 + 20 ; 4 5 int f[N]; 6 int num[3]; 7 int weight[3] = { 1 , 2 , 5 }; 8 9 int main() 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 ",i); 32 break; 33 } 34 } 35 } 36 return 0; 37 }
【题意】 给定n个物品的“价值”,“数量” 请问如果尽可能划分出两堆物品 ,要求 两堆物品间价值之差 尽可能小。 例如: 有1个物品价值为10 有2个物品价值为20 有1个物品价值为30 通过划分两堆物品 {10,30} = 40 ,{20,20} = 40 所以答案为 40 40 划分两堆物品如果价值不同,请先输出价值大的一堆,后输出价值小的一堆
【生成函数做法】 和上一个题目类似 给定物品的价值和数量,问是否存在尽可能靠近total/2的值。 首先处理好输入,初始化工作。 然后进行生成函数模板代入。 最后遍历,找出尽可能靠近total/2的情况
1 #include<cstdio> 2 const int N = 5e6 + 10; 3 const int M = 1e3 + 10; 4 int f[N] ; 5 int num[M] , w[M] ; 6 int main() 7 { 8 int n , 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 ",total-i,i); 30 break; 31 } 32 } 33 } 34 return 0 ; 35 }
【01背包做法】
经典的背包问题,01背包
首先限制背包容量为总容量的一半。
然后把所有的物品离散化,同时进行01背包处理后。
答案为: {total - f[V] , f[V]}
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 5 const int N = 5e6 + 10 ; 6 const int M = 1e3 + 10 ; 7 int f[N]; 8 int w[M]; 9 int num[M]; 10 int main() 11 { 12 int n , 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 ",total - f[V] , f[V] ); 34 } 35 return 0; 36 }
【题意】 给定价值为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<cstdio> 2 using namespace std; 3 const int M = 305 ; 4 const int N = 20 ; 5 int f[M]; 6 int v[N]; 7 int tmp[M]; 8 void Init(){ 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 int main(){ 26 27 int n ; 28 Init(); 29 while( scanf("%d",&n) , n > 0 ){ 30 printf("%d ",f[n]); 31 } 32 return 0; 33 }
【完全背包做法】 完全背包 指的是 “物品数量无限”的背包问题 这个题目其实就是和之前生成函数有所区别在于个数不再受限。 所以问题可以转变成可取无限多次的背包问题->"完全背包"
1 #include<cstdio> 2 using namespace std; 3 const int M = 305 ; 4 const int N = 20 ; 5 int f[M]; 6 int v[N]; 7 int tmp[M]; 8 void Init(){ 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 int main(){ 20 21 int n ; 22 Init(); 23 while( scanf("%d",&n) , n > 0 ){ 24 printf("%d ",f[n]); 25 } 26 return 0; 27 }
【题意】
给定n门课,每一门课都有相应的学分和数目
请问构成学分为m的方案有多少种?
【母函数做法】 和课堂上例题"整数划分问题"一模一样。 给定“学分”和“数量” 求解某学分的方案数
1 #include<cstdio> 2 using namespace std; 3 const int N = 50 ; 4 int n , m ; 5 int a[N] , b[N] , c[N] ; 6 int f[N] , tmp[N] ; 7 int main() 8 { 9 int T ; 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 ",f[n]); 31 } 32 return 0; 33 }
【多重背包做法】
给定n个物品,给定物品的“价值”和”数量“
请问构成价值为n的方案数有多少?
1 #include<cstdio> 2 using namespace std; 3 const int N = 50 ; 4 int f[N] ; 5 int a[N] , b[N] ; 6 int n , m; 7 int main() 8 { 9 int T; 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 ",f[n]); 27 } 28 return 0 ; 29 }
【题意】 给定26个字母的数量,每个字母的权值 按顺序排。 A - 1 , B - 2 …… Z - 26 请问由这些数量字母组成的单词价值<=50 的方案数?
【普通型生成函数做法】 根据题目所给定的数量进行构造生成函数。 然后模拟多项式乘法,累加系数[1,50]的方案数。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N = 30 ; 6 const int M = 50 ; 7 int f[M+10] ; 8 int tmp[M+10] ; 9 int num[N]; 10 int main() 11 { 12 int T ; 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 ",ans); 38 } 39 return 0; 40 }
【多重背包做法】
给定物品为数量,价值固定,求出价值之和<=50的方案数
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 const int N = 55; 5 const int M = 50; 6 int f[N],num[N]; 7 8 int main() 9 { 10 int T; 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 ",ans); 30 } 31 return 0; 32 }
F - Crisis of HDU
【小结】
这个题意很难读懂。
给定n个物品 的价值及数量
请问 价值为总价值的三分之一 的 方案数?
【题解】 给出物品的价格及数量。 首先判断 总价值是否能被3整除。同时计算完后,如果方案数为0还是输出"sorry"。 然后进行套母函数模板或者多重背包的模板即可 【注意】 取模运算 (a + b) % mod = a % mod + b % mod 所以过程中进行模运算不影响最后答案在mod意义下的结果
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 5 const int mod = 10000; 6 const int N = 1e3+10; 7 8 int f[N],a[N],b[N],tmp[N]; 9 int n ; 10 11 int main() 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 "); 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 ",f[total/3]); 46 }else{ 47 printf("sorry "); 48 } 49 } 50 return 0; 51 }
1 #include<cstdio> 2 const int N = 1e3 + 10; 3 const int mod = 1e4 ; 4 int f[N] , a[N] , b[N] ; 5 6 int main() 7 { 8 int n ; 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 "); 27 }else{ 28 printf("%d ",f[sum/3]); 29 } 30 } 31 return 0; 32 }
【题目来源】HDU - 2152
【题意】
题目意思是说:价值全为1的物品,然后给出n种不同物品,以及对应的数量。
但是这个数量是一个范围的概念[A,B],即物品的数量必须是[A,B]之间
求出组成价值为m的方案数
【题解】 如果这道题用母函数做,只需要修改题目中所固定的数量即可。 但如果用多重背包来做的话,背包问题默认必须是从某个起始点开始,也就是从0开始进行递推累加方案数。但是如果这个题目的话不能直接套用,但是我们可以技巧性地把问题转移到从0开始。 数量从[A,B]->[0,B-A],这样的话对应的m也需要减去各组物品的A. 然后套用模版即可。
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 const int N = 2e3 + 10 ; 5 int f[N] , tmp[N] ; 6 int main() 7 { 8 int n , m , A , B ; 9 while( scanf("%d%d",&n,&m) != EOF ){ 10 memset( f , 0 , sizeof f ); 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 ",f[m]); 26 } 27 return 0; 28 }
1 #include<cstdio> 2 #include<cstring> 3 const int N = 205; 4 int f[N] , A[N] , B[N]; 5 int main() 6 { 7 int n , m ; 8 while( scanf("%d%d",&n,&m) != EOF){ 9 memset( f , 0 , sizeof f ); 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 "); 26 else printf("%d ",f[m]); 27 } 28 return 0 ; 29 }