母函数专题
Posted 萌萌的美男子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了母函数专题相关的知识,希望对你有一定的参考价值。
1、Hdu 2082 找单词
题意:单词A-Z具有1-26的价值,现有字母A-Z的个数num[i],求问在不超过价值为五十的情况下,有多少种字母的组合数.
思路:用指数代表价值,价值又为数组的下标;用系数代表组成该价值的方案数,方案数为数组中存的值。
1 #include<iostream> 2 #include<memory.h> 3 using namespace std; 4 int re[55]; 5 int p1[55]; 6 int p2[55]; 7 int xx[27]; 8 int main() 9 { 10 int t; 11 scanf("%d", &t); 12 while (t--) 13 { 14 for (int i = 1; i <= 26; i++) scanf("%d", &xx[i]); 15 for (int i = 0; i <= 50; i++) 16 { 17 if (i <= xx[1]) p1[i]= 1; 18 else p1[i] =0; 19 re[i]=p2[i] = 0; 20 } 21 22 for (int i = 2; i <= 26; i++) 23 { 24 for (int k = 0; k <= xx[i] && k*i <= 50; k++) p2[k*i] = 1; 25 for (int j = 0; j <=50; j++) 26 { 27 for (int k = 0; k <= xx[i]; k++) 28 {//第i个字符的多项式,每项的指数为i*k,系数为1 29 int zhishu = i*k+j; 30 if (zhishu > 50) continue; 31 re[zhishu] += p1[j]*p2[k*i]; 32 } 33 } 34 for (int j = 0; j <= 50; j++) p1[j]=re[j],re[j]=p2[j]=0; 35 } 36 int sum = 0; 37 for (int i = 1; i <= 50; i++) sum += p1[i]; 38 printf("%d\n", sum); 39 } 40 return 0; 41 }
2、HDU 1028 Ignatius and the Princess III
题意:对给出的数进行拆分,求有多少种拆分方式。
思路:对于给出的n,其拆成1最多有n个,拆成2最多有n/2个,…,拆成n最多有1个。然后进行n个多项式相乘,最后指数为n的项的系数即为答案。
1 #include<iostream> 2 #include<memory.h> 3 using namespace std; 4 const int maxn = 130; 5 int re[maxn]; 6 int p1[maxn]; 7 int p2[maxn]; 8 int xx[maxn]; 9 int main() 10 { 11 int n; 12 while (~scanf("%d",&n)) 13 { 14 for (int i = 1; i <= n; i++) 15 { 16 xx[i] = n / i; 17 } 18 for (int i = 0; i <maxn; i++) 19 { 20 if (i <= xx[1]) p1[i] = 1; 21 else p1[i] = 0; 22 re[i] = p2[i] = 0; 23 } 24 25 for (int i = 2; i <= n; i++) 26 { 27 for (int k = 0; k <= xx[i] && k*i <maxn; k++) p2[k*i] = 1; 28 for (int j = 0; j <maxn; j++) 29 { 30 for (int k = 0; k <= xx[i]; k++) 31 {//第i个字符的多项式,每项的指数为i*k,系数为1 32 int zhishu = i*k + j; 33 if (zhishu >=maxn) continue; 34 re[zhishu] += p1[j] * p2[k*i]; 35 } 36 } 37 for (int j = 0; j <maxn; j++) p1[j] = re[j], re[j] = p2[j] = 0; 38 } 39 printf("%d\n", p1[n]); 40 } 41 return 0; 42 }
3、hdu1059 Dividing
题意:有num[1]个1,num[2]个2......num[6]个6,让你判断它们总和能否均分。
思路:因为总和过大,直接进行多项式相乘会超时。
如果一个数的个数为偶数,那么其显然能够均分。取一个适合的偶数,让1~6各个数字的个数对其取余,取余后的结果乘以各自的值得到sum,然后我们判断sum是否偶数,如果为奇数则说明无法均分。否则,进行多项式相乘,看最后结果中指数为sum/2的系数是否为0.
1 #include<iostream> 2 #include<memory.h> 3 using namespace std; 4 const int maxn = 120010; 5 int re[maxn]; 6 int p1[maxn]; 7 int p2[maxn]; 8 int xx[7]; 9 int main() 10 { 11 int Case = 1; 12 while (~scanf("%d%d%d%d%d%d", &xx[1],&xx[2],&xx[3],&xx[4],&xx[5],&xx[6])) 13 { 14 int count = xx[1]%60 + 2 * (xx[2]%60) + 3 *(xx[3]%60) + 4 * (xx[4]%60) + 5 * (xx[5]%60) + 6 * (xx[6]%60);//对1~6这6个数的最小公倍数取余(也可以选择其他偶数) 15 if (count == 0) break; 16 if (count % 2) 17 { 18 printf("Collection #%d:\nCan‘t be divided.\n\n", Case++); 19 continue; 20 } 21 count /= 2; 22 memset(re, 0, sizeof(re)); 23 memset(p1, 0, sizeof(p1)); 24 p1[0] = 1; 25 for (int i = 0; i <= xx[1]&&i<=count; i++) p1[i] = 1; 26 for (int i = 2; i <= 6; i++) 27 { 28 for (int j = 0; j <=count; j++) 29 { 30 for (int k = 0; k <= xx[i]&&(i*k + j<=count); k++) 31 {//第i个字符的多项式,每项的指数为i*k,系数为1 32 33 re[i*k + j] += p1[j]; 34 } 35 } 36 memcpy(p1, re, sizeof(re)); 37 memset(re, 0, sizeof(re)); 38 } 39 if (p1[count]) printf("Collection #%d:\nCan be divided.\n\n", Case++); 40 else printf("Collection #%d:\nCan‘t be divided.\n\n", Case++); 41 } 42 return 0; 43 }
4、HDU 1171 Big Event in HDU
题意:有n种物品,第一种物品的单价为v1,数量为m1;第二种物品的单价为v2,数量为m2.....现在要你将这些物品分为两堆,使得这两堆物品的价值尽量接近,输出两堆物品的价值。
思路:直接进行多项式相乘即可。记总价值为sum,最后从指数为sum/2向前找第一个系数不为0的项。
1 #include<iostream> 2 #include<memory.h> 3 using namespace std; 4 const int maxv = 125010; 5 int re[maxv]; 6 int p1[maxv]; 7 int p2[maxv]; 8 int xx[55]; 9 int main() 10 { 11 int n; 12 while (~scanf("%d",&n)) 13 { 14 if (n < 0) break; 15 int total = 0; 16 memset(xx, 0, sizeof(xx)); 17 for (int i = 1; i <= n; i++) 18 { 19 int v, w; 20 scanf("%d%d", &v, &w); 21 xx[v] += w; 22 total += v*w; 23 } 24 int totalv= total/2; 25 int st; 26 for (st = 0;xx[st]==0; st++); 27 memset(re, 0, sizeof(re)); 28 memset(p1, 0, sizeof(p1)); 29 memset(p2, 0, sizeof(p2)); 30 31 for (int i = 0; i <= totalv; i+=st) 32 { 33 p1[i] = 1; 34 } 35 for (int i = 2; i <= 50; i++) 36 { 37 if (xx[i] == 0) continue; 38 for (int k = 0; k <= xx[i] && k*i <=totalv; k++) p2[k*i] = 1; 39 for (int j = 0; j <= totalv; j++) 40 { 41 for (int k = 0; k <= xx[i]&& i*k + j<=totalv; k++) 42 {//第i个字符的多项式,每项的指数为i*k,系数为1 43 re[i*k + j] += p1[j] * p2[k*i]; 44 } 45 } 46 memcpy(p1, re, sizeof(re)); 47 memset(re, 0, sizeof(re)); 48 memset(p2, 0, sizeof(p2)); 49 } 50 int ans = totalv; 51 while (p1[ans] == 0) ans--; 52 printf("%d %d\n", total - ans, ans); 53 } 54 return 0; 55 }
5、HDU 1709 The Balance
题意:给定n个砝码的重量,总质量为sum,问在1~sum中有多少个重量不能被称出来。
思路:记下各个砝码的价值,做n次多项式乘法。 由于砝码可以两边放,所以母函数做完后做一次差值,如果指数为i和指数为j(i≤j)项系数不为0,说明可以称量出重量为i,j,也可以称出j-i。
1 #include<iostream> 2 #include<memory.h> 3 using namespace std; 4 const int maxv = 10010; 5 int re[maxv]; 6 int p1[maxv]; 7 int p2[maxv]; 8 int xx[maxv]; 9 int num[maxv]; 10 int main() 11 { 12 int n; 13 while (~scanf("%d", &n)) 14 { 15 int total = 0; 16 memset(xx, 0, sizeof(xx)); 17 for (int i = 1; i <= n; i++) 18 { 19 scanf("%d", &xx[i]); 20 total += xx[i]; 21 } 22 memset(re, 0, sizeof(re)); 23 memset(p1, 0, sizeof(p1)); 24 memset(p2, 0, sizeof(p2)); 25 for (int i = 0;i<=1;i++) 26 { 27 p1[i*xx[1]] = 1; 28 } 29 for (int i = 2; i <= n; i++) 30 { 31 for (int k = 0; k <= 1;k++) p2[k*xx[i]] = 1; 32 for (int j = 0; j <= total; j++) 33 { 34 for (int k = 0;k<=1&&xx[i]*k+j<=total; k++) 35 {//第i个字符的多项式,每项的指数为i*k,系数为1 36 re[xx[i]*k + j] += p1[j] * p2[k*xx[i]]; 37 } 38 } 39 memcpy(p1, re, sizeof(re)); 40 memset(re, 0, sizeof(re)); 41 memset(p2, 0, sizeof(p2)); 42 } 43 for (int i = 0; i <= total; i++) 44 { 45 for (int j = i + 1; j <= total; j++) 46 { 47 if (p1[i] && p1[j])re[j - i] = 1; 48 } 49 } 50 int ans = 0; 51 for (int i = 1; i <= total; i++) if (re[i] == 0) ans++; 52 printf("%d\n", ans); 53 if (ans > 0) 54 { 55 bool fst = true; 56 for (int i = 1; i <= total; i++) 57 { 58 if (re[i] == 0) 59 { 60 if (fst) 61 { 62 printf("%d", i); 63 fst = false; 64 } 65 else printf(" %d", i); 66 } 67 } 68 printf("\n"); 69 } 70 } 71 return 0; 72 }
6、HDU 1398 Square Coins
题意:用价值为1,4,9,16,…,289(1~17的平方数)的硬币,组成价值为n的方案数。
思路:由于n最大不超过300,我们可以先进行一次母函数(用300对各种硬币的价值做除得到数目,然后做17次多项式乘法),然后对每次查询输出其对应指数的系数即可。
1 #include<iostream> 2 #include<memory.h> 3 using namespace std; 4 const int maxn = 20; 5 const int maxv = 400; 6 int wp[maxn]; 7 int re[maxv]; 8 int tmp[maxv]; 9 int main() 10 { 11 for (int i = 1; i <= 17; i++) 12 { 13 wp[i] = 300 / (i*i); 14 } 15 for (int i = 0; i <= wp[1]; i++) 16 { 17 re[i] = 1*1*1; 18 } 19 for (int i = 2; i <= 17; i++) 20 { 21 for (int j = 0; j <= 300; j++) 22 { 23 for (int k = 0; k <= wp[i] && k*i*i + j <= 300; k++) 24 { 25 tmp[k*i*i + j] += re[j]*1; 26 } 27 } 28 memcpy(re, tmp, sizeof(tmp)); 29 memset(tmp, 0, sizeof(tmp)); 30 } 31 int n; 32 while (~scanf("%d", &n)&&n) 33 { 34 printf("%d\n", re[n]); 35 } 36 return 0; 37 }
7、HDU-1085 Holding Bin-Laden Captive
题意:给你三种不同价值的硬币,分别为1、2、5,告诉你各自的数量,求由这些钱币所不能组成的最小价值。
思路:对3个多项式做乘,然后从常数项往后找第一个系数为0的项。
1 #include<iostream> 2 #include<memory.h> 3 using namespace std; 4 const int maxv = 8005; 5 int re[maxv]; 6 int tmp[maxv]; 7 int main() 8 { 9 int n1, n2, n5; 10 while (~scanf("%d%d%d", &n1, &n2, &n5)) 11 { 12 if (n1 == 0 && n2 == 0 && n5 == 0) break; 13 int tsum = n1 +n2*2 + n5* 5; 14 memset(re, 0, sizeof(re)); 15 memset(tmp, 0, sizeof(tmp)); 16 for (int i = 0; i <= n1&&i * 1 <= tsum; i++) 17 { 18 re[i*1] = 1; 19 } 20 for (int i = 2; i <= 3; i++) 21 { 22 int v = (i == 2 ? 2 : 5); 23 for (int j = 0; j <= tsum; j++) 24 { 25 int num = (i == 2 ? n2 : n5); 26 for (int k = 0; k <= num&&k*v + j <= tsum; k++) 27 { 28 tmp[k*v + j] += re[j]; 29 } 30 } 31 memcpy(re, tmp, sizeof(tmp)); 32 memset(tmp, 0, sizeof(tmp)); 33 } 34 int pos = 0; 35 while (pos<=tsum&&re[pos] != 0)pos++; 36 printf("%d\n", pos); 37 } 38 return 0; 39 }
8、HDU 2110 Crisis of HDU
题意:公司此时一共有n种价值的资产,每种价值的资产数量已知,求分得到总价值1/3的方案数。
思路:如果总价值不能整除3,则方案数为0;否则,进行n-1次多项式乘法,看指数为sum/3的系数。
1 #include<iostream> 2 using namespace std; 3 struct sny 4 { 5 int p;//价值 6 int m;//数量 7 }ss[110]; 8 long long re[10005]; 9 long long tmp[10005]; 10 int main() 11 { 12 int n; 13 while (~scanf("%d", &n) && n) 14 { 15 int sum = 0; 16 for (int i = 1; i <= n; i++) 17 { 18 scanf("%d%d", &ss[i].p, &ss[i].m); 19 sum += ss[i].p*ss[i].m; 20 } 21 if (sum % 3) 22 { 23 printf("sorry\n"); 24 continue; 25 } 26 memset(re, 0, sizeof(re)); 27 memset(tmp, 0, sizeof(tmp)); 28 for (int i = 0; i <= ss[1].m&&i*ss[1].p <= sum; i++) 29 { 30 re[i*ss[1].p] = 1; 31 } 32 for (int i = 2; i <= n; i++) 33 { 34 int count = ss[i].m, val = ss[i].p; 35 for (int j = 0; j <= sum; j++) 36 { 37 for (int k = 0; k <= count&&k*val + j <= sum; k++) 38 { 39 tmp[k*val + j] =(tmp[k*val + j]+ re[j])%10000; 40 } 41 } 42 memcpy(re, tmp, sizeof(tmp)); 43 memset(tmp, 0, sizeof(tmp)); 44 } 45 if(re[sum/3]>0)printf("%lld\n", re[sum / 3] % 10000); 46 else printf("sorry\n"); 47 } 48 return 0; 49 }
9、hdu 1521 排列组合
题意:有n种物品,并且知道每种物品的数量。要求从中选出m件物品的排列数。例如有两种物品A,B,并且数量都是1,从中选2件物品,则排列有"AB","BA"两种。
思路:指数型母函数。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 long long Cal(int a) 6 { 7 long long ans = 1; 8 for (int i = 1; i <= a; i++) ans = ans*i; 9 return ans; 10 } 11 const int maxn = 1010; 12 int num[12]; 13 double p1[maxn]; 14 double p2[maxn]; 15 int main() 16 { 17 int n, m; 18 while (~scanf("%d%d", &n, &m)) 19 { 20 for (int i = 1; i <= n; i++) scanf("%d", &num[i]); 21 memset(p2, 0, sizeof(p2)); 22 memset(p1, 0, sizeof(p1)); 23 24 for (int i = 0; i <= num[1]; i++) 25 { 26 p1[i] = 1.0 / Cal(i); 27 } 28 for (int i = 2; i <= n; i++) 29 { 30 for (int k = 0; k <= m; k++) 31 { 32 for (int j = 0; j <= num[i] && j + k <= m; j++) 33 { 34 p2[j + k] += p1[k] / Cal(j); 35 } 36 } 37 for (int j = 0; j <= m; j++) 38 { 39 p1[j] = p2[j]; 40 p2[j] = 0; 41 } 42 } 43 printf("%.0lf\n", p1[m] * Cal(m)); 44 } 45 return 0; 46 }
10、hdu 2065 红色病毒(指数型母函数+泰勒级数(麦克劳林展式)+非递归快速幂)
题意:
现在有一长度为N的字符串,满足一下条件:
(1) 字符串仅由A,B,C,D四个字母组成;
(2) A出现偶数次(也可以不出现);
(3) C出现偶数次(也可以不出现);
计算满足条件的字符串个数.
当N=2时,所有满足条件的字符串有如下6个:BB,BD,DB,DD,AA,CC.
由于这个数据肯能非常庞大,你只要给出最后两位数字即可.
思路:
由4种字母组成,A和C只能出现偶数次。
构造指数型母函数:(1+x/1!+x^2/2!+x^3/3!……)^2*(1+x^2/2!+x^4/4!+x^6/6!……)^2.
前面是B和D的情况,可以任意取,但是相同字母一样,所以要除去排列数。后者是A和C的情况,只能取偶数个情况。
根据泰勒展开,e^x在x0=0点的n阶泰勒多项式为 1+x/1!+x^2/2!+x^3/3!……
而后者也可以进行调整,需要把奇数项去掉,则e^(-x)的展开式为1-x/1!+X^2/2!-X^3/3!……
所以后者可以化简为(e^x+e^(-x))/2。则原式为 (e^x)^2 * ((e^x*e^(-x))/2)^2
整理得到e^4x+2*e^2x+1。
又由上面的泰勒展开式得到
e^4x = 1 + (4x)/1! + (4x)^2/2! + (4x)^3/3! + ... + (4x)^n/n!;
e^2x = 1 + (2x)/1! + (2x)^2/2! + (2x)^3/3! + ... + (2x)^n/n!;
对于系数为n的系数为(4^n+2*2^n)/4=4^(n-1)+2^(n-1),记为答案。
1 //f(x)=(1+x/1!+x^2/2!+x^3/3!…+x^n/n!)^2+(1+x^2/2!+x^4/4!+x^6/6!…+…)^2 2 //e^x=1+x/1!+x^2/2!+x^3/3!…+x^n/n!;e^(-x)=1-x/1!+x^2/2!-x^3/3!+…-… 3 //e^x+e^(-x)=1+x^2/2!+x^4/4!+x^6/6!…+… 4 // f(x)=e^(2x) * ((e^x+e^(-x))/2)^2 5 //a(n)=(4^(n-1) + 2^(n-1)) * x^n/n! 6 //F(n) = (4 ^ (n - 1) + 2 ^ (n - 1)) % 100. 7 #include<iostream> 8 using namespace std; 9 long long quick_pow(long long a, long long b, long long md) 10 { 11 long long sum = 1; 12 while (b) 13 { 14 if (b & 1) sum = (sum*a) % md; 15 b >>= 1; 16 a = (a*a) % md; 17 } 18 return sum; 19 } 20 int main() 21 { 22 int t; 23 long long n; 24 while (~scanf("%d", &t) && t) 25 { 26 int Case = 1; 27 while (t--) 28 { 29 scanf("%lld", &n); 30 int sum = (quick_pow(4, n - 1, 100) + quick_pow(2, n - 1, 100)) % 100; 31 printf("Case %d: %d\n", Case++, sum); 32 } 33 printf("\n"); 34 } 35 return 0; 36 }
以上是关于母函数专题的主要内容,如果未能解决你的问题,请参考以下文章