普通母函数解决的大部分是组合问题
如:有1克、2克、3克、4克的砝码各一枚,能称出哪几种重量?每种重量各有几种可能方案?
(1+x)(1+x^2)(1+x^3)(1+x^4)
=(1+x+x^2+x^4)(1+x^3+^4+x^7)
=1 + x + x^2 + 2*x^3 + 2*x^4 + 2*x^5 + 2*x^6 + 2*x^7 + x^8 + x^9 + x^10
指数是重量,系数是方案数
这就是母函数
母函数的求解过程就是模拟多项式相乘的过程
附两个详细的讲解:http://www.wutianqi.com/?p=596 http://www.cnblogs.com/FCWORLD/archive/2010/10/10/1847218.html
/* hdu 1028 母函数 感觉有点动态规划的意思 就是分解一个整数的方法数 可以分解成小于等于他的数的和 */ #include<stdio.h> int a[130],b[130]; int main() { int n,i,j,k; while(scanf("%d",&n)!=EOF) { for(i=0;i<130;++i)//所有的数都可以完全由1组成,这是1种方法 { a[i]=1; b[i]=0; } for(i=2;i<=n;++i)//表示的是元素 本题中就是一个数可以被那些数字组成 1在前边已经算过了 { for(j=0;j<=n;j++) for(k=0;k+j<=n;k+=i)//对于数字j来说,加上k个i,得到一个新的数字,那个这个数字可以通过j得到,所以要加上数字j的方法数 b[k+j]+=a[j]; for(j=0;j<=n;++j)//数组进行轮换操作 { a[j]=b[j]; b[j]=0; } } printf("%d\n",a[n]); } return 0; }
/* hdu 2082 给出字母的个数,每个字母有个号,是他在字母表里的顺序 求这些字母组成的单词的数目,若只是字母顺序的差别,则认为是同一个,单词的字母号的和<=50 */ #include<stdio.h> int m[30],a[60],b[60]; int main() { int t,i,j,k; scanf("%d",&t); while(t--) { for(i=0;i<60;++i) a[i]=b[i]=0; for(i=1;i<=26;++i) scanf("%d",&m[i]); for(i=m[1];i>=0;--i)//其实也可以这样写 a[0]=1; 然后下边从i=1开始循环 a[i]=1; //本题这样写只是在初始化的时候代为计算了编号为1的字母的运算 价值为1的都可以这样 for(i=2;i<=26;++i) { for(j=0;j<=50;++j) for(k=0;(k*i+j)<=50&&k<=m[i];++k) b[k*i+j]+=a[j]; for(j=0;j<=50;j++) a[j]=b[j],b[j]=0; } j=0; for(i=1;i<=50;++i)//小于等于50的都算 j+=a[i]; printf("%d\n",j); } return 0; }
/* hdu 1398 一种特殊的货币 其值为1^2--17^2 求某数额的不同组成方法的数量 */ #include<stdio.h> int a[310],b[310]; int main() { int n,i,j,k; while(scanf("%d",&n),n) { for(i=0;i<=300;++i) a[i]=1,b[i]=0; for(i=2;i<=17;++i) { for(j=0;j<=300;++j) for(k=0;k+j<=300;k+=i*i) b[k+j]+=a[j]; for(j=0;j<=300;++j) a[j]=b[j],b[j]=0; } printf("%d\n",a[n]); } return 0; }
/* hdu 1085 1 2 5 三种硬币 给出各自的数量 求 最小的不能有这些硬币组成的金额 */ #include<stdio.h> #include<string.h> int a[10000],b[10000]; int main() { int x,y,z,i,j; while(scanf("%d%d%d",&x,&y,&z),x+z+y) { memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); for(i=0;i<=x;++i) a[i]=1; //一共就三个 就没有用最外层的循环 这是2毛的硬币 for(i=0;i<=8000;++i) for(j=0;i+j*2<=8000&&j<=y;j++) b[i+j*2]+=a[i]; for(i=0;i<=8000;++i) a[i]=b[i],b[i]=0; //这是5毛的硬币 for(i=0;i<=8000;++i) for(j=0;i+j*5<=8000&&j<=z;j++) b[i+j*5]+=a[i]; for(i=0;i<=8000;++i) a[i]=b[i],b[i]=0; i=0; while(a[i]) ++i; printf("%d\n",i); } return 0; }
/* hdu 1059 之前是用复合背包写的 现在用母函数再写一遍 发现不优化会超时 优化后有几分像背包了 题意:有六种珠宝,价值为1~6 给定他们的数量 求是否能把这些珠宝分成两份,价值相等 */ #include<stdio.h> int m[7]; int d[210000]; int z,b; int main() { int i,j,k,t=1; while(scanf("%d%d%d%d%d%d",&m[1],&m[2],&m[3],&m[4],&m[5],&m[6]),m[1]+m[2]+m[3]+m[4]+m[6]+m[5]) { z=0; for(i=1;i<=6;++i) z+=m[i]*i; if(z%2) { printf("Collection #%d:\n",t++); printf("Can't be divided.\n\n"); continue; } b=z/2; for(i=0;i<=b;++i) d[i]=0; for(i=0;i<=b&&i<=m[1];++i) d[i]=1;//价值为1的 for(i=2;i<=6;++i)//价值2~6的 { for(j=b;j>=0;j--)//为什么要反向,下边说 { if(d[j]==0) continue;//若这个价值拿不到,那么也不可能通过 这个价值+若干价值为i的珠宝 拿到更多的价值 for(k=1;k<=m[i]&&k*i+j<=b;++k) { if(d[k*i+j]) break;//下边说 d[k*i+j]=1; } } } if(d[b]) { printf("Collection #%d:\n",t++); printf("Can be divided.\n\n"); }else { printf("Collection #%d:\n",t++); printf("Can't be divided.\n\n"); } } return 0; } /* 为什么要反向做? 看过 背包九讲 的都知道,01背包空间优化的时候也是这样写的, 为的是避免多次计算 如 价值i+一定数量的价值(代表一定数量的物品)=价值j j>i 当循环到j的时候 还会通过这增加到 价值k 那么这些物品被算了两次,还可能更多次 当反向做的时候 算k的时候j还没有加那些价值 所以不会重复加了 为什么==1就break掉? 通过价值i加到j,价值j存在,那么>价值j的之前已经通过在j的基础上+若干价值访问过了 */