母函数,关键是理解多项式和问题的联系,构造合适的多项式。
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\n",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\n",count);
for(i=0;i<count;i++)
{
if(i!=count-1)printf("%d ",kk[i]);
else printf("%d\n",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;
}