母函数,关键是理解多项式和问题的联系,构造合适的多项式。
1. hdu1557: 题意:给n个数,如果其中有m个数之和大于这n个数一半,则这m个数组成这个团体叫“获胜联盟”,这m个数中,若有一个数,去掉它,这个团体就不能成为获胜联盟了,那么这个数为关键加入者。每成为一次关键加入者,则这个数的权利加1,求所有数的权利(输出)。
思路:求出所有的数组合之和(任意几个数之和)(和小于所有和二分之一即可),然后 枚举n个数,每次加一个数(原来之和小于一半,非获胜联盟),看加入后是否成为获胜联盟,若是,该数权利+该组合方案数。
母函数模型: (1+x^v[0])*(1+x^v[1])*******(1+x^v[n-1]) (v[i]表示第i个数),(每个数有两种选择,取取或不取,最终指数表示和,其系数表示该和有多少种组合方案)
#include<iostream> #include<vector> #include<cstdio> #include<cstring> int b[10000]; int a[10000]; int quanlizhishu[10000]; using namespace std; int main() { int t;cin>>t; while(t--) { memset(quanlizhishu,0,sizeof(quanlizhishu)); int n,i,sum=0;cin>>n;vector<int>v(n); for(i=0;i<n;i++) { scanf("%d",&v[i]); sum+=v[i]; } int ii,j,k,t; sum=sum/2; //求一半即可 for(ii=0;ii<n;ii++) //计算每个的权利,跑n次“母函数”,每次剔除自己这个数。 { memset(a,0,sizeof(a)); //不忘记初始化! memset(b,0,sizeof(b)); //a,b[i],表示指数为i的系数,b是最终量(目前所有多项式情况),a是中间量. b[0]=1; //b保存最终结果,初始化为开始的时候就一个1(指数为0的)。 for(i=0;i<n;i++) //这一遍扫括号,n个 { if(i==ii)continue; //不加自己。 for(j=0;j<=sum;j++) //扫目前已经乘好的多项式所有指数,b { if(b[j]==0)continue; //结果的指数为j的系数如果为0,表示没有该指数的项,跳过。 for(k=0,t=0;k+j<=sum&&t<=1;t++,k+=v[i]) //,这一遍,扫的是当前要新乘的括号,k是当前括号里指数,每循环一次乘一次,最多循环2(t控制次数)次,(每个只有2项取或不取),每次指数只有0,和v[i]. a[k+j]+=b[j]; //保存中间结果 } for(j=0;j<=sum;j++) //更新b { b[j]=a[j];a[j]=0; } } for(j=0;j<=sum;j++) //计算权利,加入v[i后,是否成为获胜联盟, { if(j+v[ii]>sum)quanlizhishu[ii]+=b[j]; //每次权利加上组合方案数 } } for(i=0;i<n;i++) { if(i!=n-1) printf("%d ",quanlizhishu[i]); else printf("%d ",quanlizhishu[i]); } } return 0; }
2.hdu1028 任意给你一个整数,问有多少种拆分方案。
母函数典例。(1+x^1+x^2+...)*(1+x^2+x^4....)*(1+x^3+x^6+....)。。。第一个括号取1,指数表示个数,0表示不取,其后依次。
#include<iostream> #include<cstring> using namespace std; int a[200]; int b[200]; int main() { int n; while(cin>>n) { int i,j,k; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); b[0]=1; for(i=1;i<=n;i++) //n个括号,最多到n, { for(j=0;j<=n;j++) //目前多项式 { if(b[j]==0)continue; for(k=0;k+j<=n;k+=i) //新括号i { a[j+k]+=b[j]; } } for(j=0;j<=n;j++) { b[j]=a[j]; a[j]=0; } } cout<<b[n]<<endl; //指数为n的个数(系数)。 } return 0; }
3.hdu1398 平方数和的组合数。基数是平方数即可。无限类型,自己看上限多少。适可而止。
多项式:(1+x^1+x^2+...)*(1+x^4+x^8....)*(1+x^9+x^18+....)...指数表基数,系数是方案数
#include<iostream> #include<cstring> using namespace std; int a[301]; int b[301]; int v[18]; int main() { int n;int p=1; for(p=1;p<18;p++) //先保存基数。 { v[p]=p*p; } while(cin>>n&&n) { int i,j,k; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); b[0]=1; for(i=1;i<18;i++) { for(j=0;j<=n;j++) { if(b[j]==0)continue; for(k=0;k+j<=n;k+=v[i]) { a[j+k]+=b[j]; } } for(j=0;j<=n;j++) { b[j]=a[j]; a[j]=0; } } cout<<b[n]<<endl; } return 0; }
hoj1085,有限个基数情况,基数为1,2,5,每个个数为题目输入。
多项式:(1+x^1+x^2+..)*(1+x^2+x^4+...)*(1+x^5+x^10+..)每个的项数为所给的个数+1(可不取)。响应处理技巧见代码。
#include<iostream> #include<cstring> using namespace std; int a[9001]; int b[9001]; int main() { int n;int x,y,z; while(cin>>x>>y>>z&&(x||y||z)) { int i,j,k;int t; int min=-1;bool mark=1; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); b[0]=1; int c[4]={0,1,2,5}; //基数数组 int d[4]={0,x,y,z}; //用一个数组来对应第i个括号项数。 for(i=1;i<4;i++) { int maxzhishu; if(i==1)maxzhishu=x; //上限优化, else if(i==2)maxzhishu=x+2*y; else maxzhishu=x+2*y+5*z; for(j=0;j<=maxzhishu;j++) //b[i]中的指数扫一遍. { for(k=0,t=0;k+j<=maxzhishu&&t<=d[i];k+=c[i],t++) //取的次数为个数 { a[j+k]+=b[j]; } if(i==1)break; } for(j=0;j<=maxzhishu;j++) { b[j]=a[j]; if(b[j]==0){min=j;break;mark=0;} //输出第一个无法实现的组合 a[j]=0; } if(mark==0)break; } if(min==-1)cout<<x+2*y+5*z+1<<endl; else cout<<min<<endl; } return 0; }
hdu 1171,每个物品有价值和数量。都由输入给出。把所有物品分给2个人(单件物品不可拆分),要求尽量公平(价值差距尽量小).
母函数可以求出价值和的所有可能情况,然后总价值除以2,从这开始找存在的方案。第一个找到的(离中点最近),是最公平的。
母函数:不贴了。
#include<iostream> #include<vector> #include<cstring> using namespace std; int a[500001]; int b[500001]; struct vv { int x; int count; }; int main() { int n; while(cin>>n&&n>=0) { int i,j,k;int t;int marks; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); b[0]=1; vector<vv>v(n+1); for(i=1;i<=n;i++) { cin>>v[i].x>>v[i].count; } int maxzhishu=0; for(i=1;i<=n;i++) { maxzhishu+=v[i].x*v[i].count; for(j=0;j<=maxzhishu;j++) //b[i]中的指数扫一遍. { if(b[j]==0)continue; //如果指数为0,不存在该项。 for(k=0,t=0;k+j<=maxzhishu&&t<=v[i].count;k+=v[i].x,t++) { a[j+k]+=b[j]; } if(i==1)break;//第一个括号只要乘一次. } for(j=0;j<=maxzhishu;j++) { b[j]=a[j]; a[j]=0; } } //此时max指数是所有价值之和。 for(i=maxzhishu/2;i>=0;i--) //找到一半之后开始早到第一个有组合数的(存在的方案) if(b[i]){ marks=i;break;} //现在看来这个marks好像没用... cout<<maxzhishu-i<<" "<<i<<endl; } return 0; }
hdu 2079 有限数量组合。
#include<iostream> #include<vector> #include<cstring> using namespace std; int a[10001]; int b[10001]; struct vv //有限项组合,每项指数与数量用结构体击杀! { int x; int count; }; int main() { int n;int tt;cin>>tt; while(tt--) { int t; int mubiao;cin>>mubiao; int i,j,k; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); b[0]=1;cin>>n; vector<vv>v(n+1); for(i=1;i<=n;i++) { cin>>v[i].x>>v[i].count; } int maxzhishu=0; for(i=1;i<=n;i++) { maxzhishu+=v[i].x*v[i].count; //最大指数优化 if(maxzhishu>mubiao)maxzhishu=mubiao; for(j=0;j<=maxzhishu;j++) //b[i]中的指数扫一遍. { if(b[j]==0)continue; //如果指数为0,不存在该项。 for(k=0,t=0;k+j<=maxzhishu&&t<=v[i].count;k+=v[i].x,t++) { a[j+k]+=b[j]; } if(i==1)break;//第一个括号只要乘一次. } for(j=0;j<=maxzhishu;j++) { b[j]=a[j]; a[j]=0; } } cout<<b[mubiao]<<endl; } return 0; }
hdu2082,有限,每个有价值,典例。
#include<iostream> #include<vector> #include<cstring> using namespace std; int a[101]; int b[101]; struct vv //有限项组合,每项指数与数量用结构体击杀! { int x; int count; }; int main() { int n;int tt;cin>>tt; while(tt--) { int t; int i,j,k; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); b[0]=1; vector<vv>v(27); for(i=1;i<=26;i++) { v[i].x=i; cin>>v[i].count; } int maxzhishu=0; for(i=1;i<=26;i++) { maxzhishu+=v[i].x*v[i].count; if(maxzhishu>50)maxzhishu=50; for(j=0;j<=maxzhishu;j++) //b[i]中的指数扫一遍. { if(b[j]==0)continue; //如果指数为0,不存在该项。 for(k=0,t=0;k+j<=maxzhishu&&t<=v[i].count;k+=v[i].x,t++) { a[j+k]+=b[j]; } if(i==1)break;//第一个括号只要乘一次. } for(j=0;j<=maxzhishu;j++) { b[j]=a[j]; a[j]=0; } } int sum=0; for(i=1;i<=50;i++) if(b[i])sum+=b[i]; cout<<sum<<endl; } return 0; }
hdu 2110,数量有限,价值,
#include<iostream> #include<vector> #include<cstring> using namespace std; __int64 a[1001]; __int64 b[1001]; struct vv //有限项组合,每项指数与数量用结构体击杀! { int x; int count; }; int main() { int n;int tt; while(cin>>n&&n) { int t; int mubiao=0; int i,j,k; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); b[0]=1; vector<vv>v(n+1); for(i=1;i<=n;i++) { cin>>v[i].x>>v[i].count; mubiao=mubiao+v[i].x*v[i].count; } if(mubiao%3!=0){cout<<"sorry"<<endl;continue;} mubiao/=3; int maxzhishu=0; for(i=1;i<=n;i++) { maxzhishu+=v[i].x*v[i].count; if(maxzhishu>mubiao)maxzhishu=mubiao; for(j=0;j<=maxzhishu;j++) //b[i]中的指数扫一遍. { if(b[j]==0)continue; //如果指数为0,不存在该项。 for(k=0,t=0;k+j<=maxzhishu&&t<=v[i].count;k+=v[i].x,t++) { a[j+k]+=b[j]; } if(i==1)break;//第一个括号只要乘一次. } for(j=0;j<=maxzhishu;j++) { b[j]=a[j]%10000; a[j]=0; } } if(b[mubiao]) cout<<b[mubiao]<<endl; else cout<<"sorry"<<endl; } return 0; }
类似:普通型母函数hdu 2152,2189。无非是基数变化,数量变化。
不贴了。
hdu1709 用一个天平称东西,每一定种类的砝码各一个,问哪些重量称不出来,(砝码俩边可放),这题是有负指数母函数,下表不能负,所以每个加100(最大负),总的加100*N+sum。
母函数:(x^-p[0]+1+x^p[0])*(......).....此时,负表放在物品那一边,正数是放在砝码一边,或者不放。
#include<iostream> #include<cstring> #include<cstdio> using namespace std; int a[30005];int b[30005]; int main() { int i,j,t,k,n; while(scanf("%d",&n)!=EOF) { int p[105]; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); int sum=0; for(i=1;i<=n;i++) { scanf("%d",&p[i]); sum+=p[i]; } b[0]=1; for(i=1;i<=n;i++) { for(j=0;j<=n*100+sum;j++) //加100处理负数(最多-100)情况,每个加100,最大指数可能n*100+sum. { if(b[j]==0)continue; for(k=-1*p[i]+100,t=0;k+j<=n*100+sum&&t<3;k=k+p[i],t++) //关键,来一个新的都加100.共加了n*100 { a[j+k]+=b[j]; } if(i==1)break; } for(j=0;j<=n*100+sum;j++) { b[j]=a[j]; a[j]=0; } } int count=0;int kk[10005]; memset(kk,0,sizeof(kk)); for(i=n*100;i<=n*100+sum;i++) { if(b[i]==0){kk[count]=i-n*100;count++;} } printf("%d ",count); for(i=0;i<count;i++) { if(i!=count-1)printf("%d ",kk[i]); else printf("%d ",kk[i]); } } return 0; }
hdu 1521 n种物品,每种xi个,取r个的排列数,显然这个是排列问题,用指数型母函数(需要先学习指数型母函数)。
该题: (1+x+x^2/2!+x^3/3!+...x^xi/xi!)*(1+...)
#include<iostream> #include<vector> #include<cstring> #include<iomanip> using namespace std; double a[50]; //指数型的,系数用double型, double b[50]; double fa[50]; void f() //阶乘 { fa[0]=1; fa[1]=1; for(int i=2;i<50;i++) fa[i]=fa[i-1]*i; } int main() { f(); int n,r,i,j,k,t; while(cin>>n>>r) { vector<int>v(n+1); for(i=1;i<=n;i++) cin>>v[i]; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); b[0]=1; for(i=1;i<=n;i++) { for(j=0;j<=r;j++) //扫指数 { if(b[j]==0)continue; for(k=0,t=0;k+j<=r&&t<=v[i];k++,t++) //新来项,k次 { a[j+k]=a[j+k]+b[j]/fa[k]; //每次 除以次数的阶乘,(不同点一) } if(i==1)break; } for(j=0;j<=r;j++) { b[j]=a[j]; a[j]=0; } } cout<<fixed<<setprecision(0)<<b[r]*fa[r]<<endl; //最终先乘以阶乘,并取整即可(不同点二) } return 0; }