: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>
太晚了,先寫這麼多吧