:http://blog.csdn.net/hhq420684/article/details/12876993
http://blog.csdn.net/YDYKL/article/details/6655142
http://blog.csdn.net/leonharetd/article/details/8922262
http://blog.csdn.net/lac001001/article/details/45137681
http://blog.csdn.net/MetalSeed/article/details/8046656
http://blog.csdn.net/zhongshijunacm/article/details/17481181
1,把组合问题的加法法则和幂级数的乘幂对应起来
2,把离散数列和幂级数一一对应起来,把离散数列见的相互结合关系对应成为幂级数间的运算关系,最后由幂级数形式来确定离散数列的构造
一:在谈论母函数问题之前,我们先看一个简单的问题描述:假如有两组数据(A,B)和(C,D),每组中选出一个构成一个组合,总共有几种选法?很显然总共有4种选法:AC,AD,BC,BD。而且很容易联想到这个式子(A+B)*(C+D)=A*C+A*D+B*C+B*D。式子中的几个乘积项就是上面的4种选法。假如把问题换一下:每组中选出一个或0个数据构成组合,总共有几种组合?那么结果就变成:{空},A,B,C,D,AC,AD,BC,BD,而式子(1+A+B)*(1+C+D)=1+C+D+A+A*C+A*D+B+B*C+B*D,正好和上面组合的结果又一致(1代表什么都没选)。从这2个例子我们可以发现多项式乘积和组合存在着某种关系。事实上我们可以这么理解:(1+A+B)可以理解为从第一组数据中取0个数据,取A或者取B,同样(1+C+D)可以理解为从第二组数据取0个数据,取C或者取D。两者相乘的结果就表示了所有的组合。再看一下这个多项式:
(1+x)*(1+x+x2)*(1+x3)=1+2x+2x2+2x3+2x4+2x5+x6
这个多项式和上面的有一些区别了,它的幂级数超过1了。如果要从(1+x)、(1+x+x2)和(1+x3)中得到x的2次方的话,有两种选择:从(1+x)和(1+x+x2)中分别选择一个x或者从(1+x+x2)中选择x2;如果要得到x的6次方的话,只有1种选择,就是从(1+x)中选择x、(1+x+x2)中选择x2、(1+x3)中选择x3。也就是说乘积结果的每一项anxn的前面的系数an表示了从(1+x)、(1+x+x2)和(1+x3)中得到xn的组合数。
其实上面的例子就利用了母函数的思想,下面来具体讨论一下母函数
二:母函数就是n个多项式相乘,最终化为G(x)=a0+a1x+a2x^2+a3x^3+a4x^4+……+anx^n的式子,相应的系数对应相应的组成方式的个数。这个就是普通型母函数
三利用普通型母函数来解决一些组合问题
1. 有1克、2克、3克、4克砝码各一枚,问你能称出哪几种重量?每种重量各有几种方案?
下面是用母函数解决这个问题的思路:
首先,我们用X表示砝码,X的指数表示砝码的重量。那么,如果用函数表示每个砝码可以称的重量,
1个1克的砝码可以用函数X^0 + X^1表示,x^0代表不放的状态 x^1代表放一个1克的砝码。
1个2克的砝码可以用函数X^0 + X^2表示,x^0代表不放的状态 x^2代表放一个2克的砝码。
依次类推。
所以母函数 为 (1 + x) * (1 + x^2 ) * (1 + x^3 ) * (1 + x^4 )
解得方程 X^0 + X^1 + X^2 + 2*X^3 + 2*X^4 + 2*X^5 + 2*X^6 + 2*X^7 + X^8 + X^9 + X^10。
因为同底数幂相乘指数相加,所以 x^3*x^2 = x^5 说明一个3克砝码和一个2克砝码称出了5克,x^1*x^4 = x^5一个1克砝码和一个4克砝码称出了5克.
说明 该多项式的系数代表他解的个数 2*x^6代表称出6克有两种方案
2.拆分整数以展开后的x为例,其系数为4,即4拆分成1、2、3之和的拆分数为4;
即 :4=1+1+1+1=1+1+2=1+3=2+2
这里再引出两个概念整数拆分和拆分数:
所谓整数拆分即把整数分解成若干整数的和(相当于把n个无区别的球放到n个无标志的盒子,盒子允许空,也允许放多于一个球)。
整数拆分成若干整数的和,办法不一,不同拆分法的总数叫做拆分数。
可以构造母函数G( x ) = ( 1 + x + x^2 +····) * ( 1 + x^2 + x^4 + ····) * ( 1 + x^3 + x^6),最终x^4d的系数为4的基本拆分总数。
下面是模板代码:
<span style="font-size:18px;">#include<iostream>
using namespace std;
int main()
{
int c1[1000];
int c2[1000];
int n;
while (cin >> n)
{
for (int i = 0; i <= n; i++)
{
c1[i] = 1;//c1为已经计算得到的结果
c2[i] = 0;//c2为正在计算的状态结果
}
for (int i = 2; i <= n; i++)//计算第一个多项式依次和后面的多项式相乘
{
for (int j = 0; j <= n; j++)//从前面一个多项式中X的0——n次方系数一次计算
{
for (int k = 0; k + j <= n; k += i)//后面一个多项式次方数一次为0,k+i,k+2i,x^0,x^2,x^4;x^n。因为只计算到x^n次方,所以k+j要小于n
{
c2[j + k] +=1* c1[j];//元素下标即为x的次方数.1代表第二项x^k的系数为1.x的k次方与x的j次方相乘的值放在x^(j+k)中
}//共有n个多项式,只计算保留x^n前的系数,因为需要x^n的系数
}
for (int i = 0; i <= n; i++)//赋值给c2
{
c1[i] = c2[i];
c2[i] = 0;
}
}
cout << c1[n] << endl;
}
return 0;
}</span>
这就是基本的模板代码
下面介绍杭电上的一些题目:
1.1. 题目:http://acm.hdu.edu.cn/showproblem.php?pid=1028
就是直接运用模板AC代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
int main()
{
int c1[1000];
int c2[1000];
int n;
while (cin >> n)
{
for (int i = 0; i <= n; i++)
{
c1[i] = 1;
c2[i] = 0;
}
for (int i = 2; i <= n; i++)//计算第一个多项式依次和后面的多项式相乘
{
for (int j = 0; j <= n; j++)//从前面一个多项式中X的0——n次方系数一次计算
{
for (int k = 0; k + j <= n; k += i)//后面一个多项式次方数一次为0,k+i,k+2i,x^0,x^2,x^4;x^n
{
c2[j + k] +=1* c1[j];//元素下标即为x的次方数.1代表第二项x^k的系数为1.x的k次方与x的j次方相乘的值放在x^(j+k)中
}//共有n个多项式,只计算保留x^n前的系数,因为需要x^n的系数
}
for (int i = 0; i <= n; i++)
{
c1[i] = c2[i];
c2[i] = 0;
}
}
cout << c1[n] << endl;
}
return 0;
}
</span>
2. 题目:http://acm.hdu.edu.cn/showproblem.php?pid=1398
这题基本是套模板,就是在循环条件略微改了一下
ac代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
int main()
{
// int key[400];
int n;
while(cin>>n&&n)
{
int c1[400];
int c2[400];
for(int m=0;m<=n;m++)
{
c1[m]=1;
c2[m]=0;
}
for(int i=2;i*i<=n;i++)
{
for(int j=0;j<=n;j++)
{
for(int k=0;j+k<=n;k+=i*i)//注意循环条件
{
c2[j+k]+=1*c1[j];
}
}
for(int v=0;v<=n;v++)
{
c1[v]=c2[v];
c2[v]=0;
}
}
cout<<c1[n]<<endl;
}
return 0;
}
</span>
3 http://acm.hdu.edu.cn/showproblem.php?pid=1085
建议这题数组开大点,这题先把所以组合情况记录下来,找到系数为0的指数就是无法组成的,若都可以的话,那sum+1肯定不可以,是最小了的
AC代码
<span style="font-size:18px;">#include<iostream>
#include<string>
#include<cstring>
using namespace std;
int main()
{
int c1[110000];
int c2[110000];
int a[3] = { 1, 2, 5 };
int b[3];
while (cin >> b[0]>> b[1]>> b[2])
{
if (b[0] == 0 && b[1] == 0 && b[2] == 0)
{
break;
}
memset(c1, 0, sizeof(c1));
memset(c2, 0, sizeof(c2));
int sum = b[0] + b[1]* 2 + b[2] * 5;
for (int i = 0; i <= b[0]; i++)
{
c1[i] = 1;
}
for (int i = 1; i < 3; i++)
{
for (int j = 0; j <= sum; j++)
{
for (int k = 0, v = 0; v <= b[i]; k += a[i],v++)
{
c2[k + j] += c1[j];
}
}
for (int j = 0; j <= sum; j++)
{
c1[j] = c2[j];
c2[j] = 0;
}
}
int i;
for ( i = 0; i <= sum; i++)
{
if (c1[i] == 0)
{
cout << i << endl;
break;
}
}
if (i == sum+1)
{
cout << sum + 1 << endl;
}
}
return 0;
}</span>
4. http://acm.hdu.edu.cn/showproblem.php?pid=1709
给你天平和砝码,让你判断1到s之间的重量那个是不可以求得,s为所有砝码的质量之和,这题要注意砝码既可以放一边,也可以两边都放,那么就是既有加的情况,也有减的情况。
AC代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
int abs(int n)
{
return n<0?(-n):n;
}
int main()
{
int c1[15000];
int c2[15000];
int a[101];
int result[10000];
int n;
while(cin>>n)
{
int i,j,k;
int sum=0;
memset(c1,0,sizeof(c1));
memset(c2,0,sizeof(c2));
for( i=0;i<n;i++)
{
cin>>a[i];
sum+=a[i];
}
for( i=0;i<=a[0];i+=a[0])
{
c1[i]=1;
}
for(i=1;i<n;i++)
{
for(j=0;j<=sum;j++)
{
for(k=0;k<=a[i];k+=a[i])
{
c2[k+j]+=c1[j];
c2[abs(k-j)]+=c1[j];
}
}
for(j=0;j<=sum;j++)
{
c1[j]=c2[j];
c2[j]=0;
}
}
int count=0;
for(i=0;i<=sum;i++)
{
if(c1[i]==0)
{
result[count++]=i;
}
}
cout<<count<<endl;
if(count!=0)
{
for(i=0;i<count-1;i++)
{
cout<<result[i]<<' ';
}
cout<<result[count-1]<<endl;
}
}
return 0;
}
</span>
5. http://acm.hdu.edu.cn/showproblem.php?pid=1171
这题同样用母函数来解,只不过注意指数的变化范围,还有要求的是sum/2左右系数不为0的指数
AC代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
struct f
{
int v;
int num;
}st[70];
int c1[260000];
int c2[260000];
int main()
{
int n,len;
while (cin >> n&&n > 0)
{
len = 0;
for (int i = 0; i < n; i++)
{
cin >> st[i].v >> st[i].num;
len += st[i].v*st[i].num;
}
memset(c1, 0, len*sizeof(c1[0]));
memset(c2, 0, len*sizeof(c2[0]));
for (int i = 0; i <= st[0].num*st[0].v; i+=st[0].v)
{
c1[i] = 1;
}
for (int i = 1; i <= n-1; i++)
{
for (int j = 0; j <= len; j++)
{
for (int k = 0;(k<=st[i].num*st[i].v)&& (k+j <= len); k += st[i].v)
{
c2[j + k] += 1 * c1[j];
}
}
for (int i = 0; i <= len; i++)
{
c1[i] = c2[i];
c2[i] = 0;
}
}
int v;
for ( v = (len >> 1); v >= 0; v--)
{
if (c1[v]!=0)
{
break;
}
}
cout << len - v<< ' ' << v << endl;
}
return 0;
}</span>
6. http://acm.hdu.edu.cn/showproblem.php?pid=2069
给你1,5,10,25,50不同的硬币,在给你一个钱数,问这些硬币组成这个钱数有多少种不同的方法,但是要求所需的总的硬币个数要小于等于100
AC代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
int main()
{
int n;
int c1[500][102];//前面表示硬币能够表示的钱数,后面表示表示这个钱数需要多少硬币
int c2[500][102];//前面表示硬币能够表示的钱数,后面表示表示这个钱数需要多少硬币
int a[5]={1,5,10,25,50};
while(cin>>n)
{
memset(c1,0,sizeof(c1));
memset(c2,0,sizeof(c2));
int i,j,k;
for(i=0;i<=100;i++)
{
c1[i][i]=1;
}
for( i=1;i<5;i++)
{
for(j=0;j<=n;j++)
{
for(k=0;k+j<=n;k+=a[i])
{
for(int v=0;v+k/a[i]<=100;v++)//判断所需硬币数量有没有超过100
c2[k+j][v+k/a[i]]+=c1[j][v];//表示同一钱币所以不同的硬币数量相加
}
}
for(k=0;k<=n;k++)
{
for(j=0;j<102;j++)
{
c1[k][j]=c2[k][j];
c2[k][j]=0;
}
}
}
int sum=0;
for(i=0;i<=100;i++)
{
sum+=c1[n][i];//得到钱数小于100总的表示方法
}
cout<<sum<<endl;
}
return 0;
}</span>
7 . http://acm.hdu.edu.cn/showproblem.php?pid=2152
AC代码 同样简单母函数
<span style="font-size:18px;">#include<iostream>
using namespace std;
int main()
{
int n,m;
int a[101][2];
int c1[200];
int c2[200];
while(cin>>n>>m)
{
int i,j,k;
for(i=0;i<n;i++)
{
cin>>a[i][0]>>a[i][1];
}
memset(c1,0,sizeof(c1));
memset(c2,0,sizeof(c2));
for(i=a[0][0];i<=a[0][1];i++)
{
c1[i]=1;
}
for(i=1;i<n;i++)
{
for(j=0;j<=m;j++)
{
for(k=a[i][0];k<=a[i][1];k++)
{
c2[j+k]+=c1[j];
}
}
for(j=0;j<=m;j++)
{
c1[j]=c2[j];
c2[j]=0;
}
}
cout<<c1[m]<<endl;
}
return 0;
}</span>
8.http://acm.hdu.edu.cn/showproblem.php?pid=2566
和上面一题2069一样
AC代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
int c1[1000][1000];
int c2[1000][1000];
int main()
{
int n,m,t;
int a[3]={1,2,5};
int i,j,k,v;
cin>>t;
while(t--)
{
cin>>n>>m;
memset(c1,0,sizeof(c1));
memset(c2,0,sizeof(c2));
for( i=0;i<=n;i++)
{
c1[i][i]=1;
}
for(i=1;i<3;i++)
{
for(j=0;j<=m;j++)
{
for(k=0;k+j<=m;k+=a[i])
{
for(v=0;v+k/a[i]<=n;v++)
{
c2[k+j][v+k/a[i]]+=c1[j][v];
}
}
}
for(j=0;j<=m;j++)
{
for(k=0;k<=n;k++)
{
c1[j][k]=c2[j][k];
c2[j][k]=0;
}
}
}
cout<<c1[m][n]<<endl;
}
return 0;
}</span>
9.http://acm.hdu.edu.cn/showproblem.php?pid=2189
注意组成数为素数,所以次方相加为素数的倍数
AC代码
<span style="font-size:18px;">#include<iostream>
using namespace std;
int prime[100];
bool isprime(int n)
{
for(int i=2;i*i<=n;i++)
{
if(n%i==0)
return false;
}
return true;
}
int main()
{
int key=0;
int i,j,k,t;
for(i=2;i<=150;i++)
{
if(isprime(i))
{
prime[key++]=i;
}
}
int n;
cin>>t;
int c1[200];
int c2[200];
while(t--)
{
cin>>n;
memset(c1,0,sizeof(c1));
memset(c2,0,sizeof(c2));
for(i=0;i<=n;i+=prime[0])
{
c1[i]=1;
}
for(i=1;i<key;i++)
{
for(j=0;j<=n;j++)
{
for(k=0;k+j<=n;k+=prime[i])
{
c2[j+k]+=1*c1[j]+0*c2[k];
}
}
for(j=0;j<=n;j++)
{
c1[j]=c2[j];
c2[j]=0;
}
}
cout<<c1[n]<<endl;
}
return 0;
}</span>
太晚了,先写这么多吧