一篇写的很不错的博客链接:https://blog.csdn.net/xiaofei_it/article/details/17042651
母函数通常解决类似如下的问题:
给5张1元,4张2元,3张5元,要得到15元,有多少种组合?
某些时候会规定至少使用3张1元、1张2元、0张5元。
某些时候会规定有无数张1元、2元、5元。
……
其实母函数的问题大部分也可以用dp/背包来写,但是我太弱了 感觉dp容易写错 有个母函数这样比较容易理解的模板更好。
接下来是几道我母函数入门的题目:
我这里都运用了last标记来优化母函数的效率,这样效率更高。
HDU1085
题意:面值分别为1,2,5的硬币,给出它们的数量。问不能组成的最小金额是多少。
思路:母函数跑出金额为n的组合方案数有多少种,从1开始遍历,第一个方案数为 0 的n就是答案。
代码:
1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #define mem(a, b) memset(a, b, sizeof(a)) 5 using namespace std; 6 7 int val[5], num[5], last, last2; 8 int temp[8100], ans[8100]; 9 10 int main() 11 { 12 val[0] = 1, val[1] = 2, val[2] = 5; 13 while(scanf("%d%d%d", &num[0], &num[1], &num[2]) != EOF) 14 { 15 if(num[0] + num[1] + num[2] == 0) 16 break; 17 mem(ans, 0), mem(temp, 0); 18 last = 0, ans[0] = 1; 19 for(int i = 0; i < 3; i ++) 20 { 21 last2 = min(last + val[i] * num[i], 8100); 22 memset(temp, 0, sizeof(int) * (last2 + 1)); 23 for(int j = 0; j <= num[i] && j * val[i] <= last2; j ++) 24 { 25 for(int k = 0; k <= last && k + j * val[i] <= last2; k ++) 26 temp[k + j * val[i]] += ans[k]; 27 } 28 memcpy(ans, temp, sizeof(int) * (last2 + 1)); 29 last = last2; 30 } 31 for(int i = 0; i <= 8010; i ++) 32 { 33 if(ans[i] == 0) 34 { 35 printf("%d ", i); 36 break; 37 } 38 } 39 } 40 return 0; 41 }
HDU2079
题意:有k行,每行有两个整数a(1 <= a <= 8),b(1 <= b <= 10),表示学分为a的课有b门。输出一个整数,表示学n个学分的组合数。
代码:
1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #define mem(a, b) memset(a, b, sizeof(a)) 5 using namespace std; 6 7 int n, k; 8 int val[10], num[10]; 9 int last, last2, temp[50], ans[50]; 10 11 int main() 12 { 13 int T; 14 scanf("%d", &T); 15 while(T --) 16 { 17 last = 0; 18 mem(temp, 0), mem(ans, 0); 19 ans[0] = 1; 20 scanf("%d%d", &n, &k); 21 for(int i = 0; i < k; i ++) 22 scanf("%d%d", &val[i], &num[i]); 23 for(int i = 0; i < k; i ++) 24 { 25 last2 = min(last + val[i] * num[i], n); 26 memset(temp, 0, sizeof(int) * (last2 + 1)); 27 for(int j = 0; j <= num[i] && j * val[i] <= last2; j ++) 28 { 29 for(int k = 0; k <= last && k + j * val[i] <= last2; k ++) 30 { 31 temp[k + j * val[i]] += ans[k]; 32 } 33 } 34 memcpy(ans, temp, sizeof(int) * (last2 + 1)); 35 last = last2; 36 } 37 printf("%d ", ans[n]); 38 } 39 return 0; 40 }
HDU2152
题意:n种水果,求出m个水果的组合方案数。但是这里有数量限制,有下限跟上限
代码:
1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #define mem(a, b) memset(a, b, sizeof(a)) 5 using namespace std; 6 7 int n, m, down[110], up[110]; 8 int last, last2; 9 int ans[110], temp[110]; 10 11 int main() 12 { 13 while(scanf("%d%d", &n, &m) != EOF) 14 { 15 for(int i = 0; i < n; i ++) 16 scanf("%d%d", &down[i], &up[i]); 17 mem(temp, 0), mem(ans, 0); 18 ans[0] = 1, last = 0; 19 for(int i = 0; i < n; i ++) 20 { 21 last2 = min(last + up[i], m); 22 memset(temp, 0, sizeof(int) * (last2 + 1)); 23 for(int j = down[i]; j <= up[i] && j <= last2; j ++) 24 { 25 for(int k = 0; k <= last && k + j <= last2; k ++) 26 { 27 temp[k + j] += ans[k]; 28 } 29 } 30 memcpy(ans, temp, sizeof(int) * (last2 + 1)); 31 last = last2; 32 } 33 printf("%d ", ans[m]); 34 } 35 return 0; 36 }
模板:
1 ans[0] = 1, last = 0; //初始化 多组输入的话还要 mem(ans, 0), mem(temp, 0) 2 for(int i = 0; i < k; i ++) 3 { 4 last2 = min(last + val[i] * num[i], n);//计算下一次的last 5 memset(temp, 0, sizeof(int) * (last2 + 1));//只清空temp[0...last2] 6 for(int j = down[i]; j <= up[i] && j * val[i] <= last2; j ++)//遍历因子中的每一项 注意数量限制 上下限 7 { 8 for(int k = 0; k <= last && k + j * val[i] <= last2; k ++)//遍历答案数组,更新答案,因子乘之前因子相乘得到的临时答案 9 {//ans里是上一次的last,temp里是last2 10 temp[k + j * val[i]] += ans[k]; 11 } 12 } 13 memcpy(ans, temp, sizeof(int) * (last2 + 1));//temp赋值给ans 作为临时答案 待更新 14 last = last2;//更新last 15 }