普通型母函数
生成函數(母函數)有普通生成函數和指數生成函數:
1.普通生成函數用于解決多重集的組合問題
2.指數型母函數用于解決多重集的排列問題
母函數可以解決遞歸數列的通項問題:斐波那契數列、卡特蘭數列等
普通母函數:
??? 構造母函數G(x), G(x) = a0 + a1*x + a2*?+ a3*?+....+ an*,? 則稱G(x)是數列a0,a1…an的母函數。
??? 通常普通母函數用來解多重集的組合問題,其思想就是構造一個函數來解決問題,一般過程如下:
??? 1.建立模型:
物品n種,每種數量分別為k1,k2,..kn個,每種物品又有一個屬性值p1,p2,…pn,(如本題的字母價值),求屬性值和為m的物品組合方法數。(若數量ki無窮 也成立,即對應下面式子中第ki項的指數一直到無窮)
??? 2.構造母函數:
G(x)=(1++…)(1+++…)…(1+++…)? ? ? ? ?(一)
? ? ? ? ? =a0 + a1*x + a2*?+ a3*?+....+ akk*???? (設kk=k1·p1+k2·p2+…kn·pn)? ? ? ? ?(二)
????????????????? G(x)含義: ak 為屬性值和為k的組合方法數。
母函數利用的思想:
??? 1.把組合問題的加法法則和冪級數的乘冪對應起來。
??? 2.把離散數列和冪級數對應起來,把離散數列間的相互結合關系對應成為冪級數間的運算關系,最后由冪級數形式來
?????? 確定離散數列的構造。
代碼實現:
??? 求G(x)時一項一項累乘。先令G=1=(1+0*x+0*+…0*),再令G=G*(1++…)得到形式(二)的式子…最后令G=G*(1+++…)。
普通母函數通常解決類似如下的問題:
給5張1元,4張2元,3張5元,要得到15元,有多少種組合?
某些時候會規定至少使用3張1元、1張2元、0張5元。
某些時候會規定有無數張1元、2元、5元。
……
解題過程:
首先要寫出表達式,通常是多項式的乘積,每項由多個??組成。
通用表達式為:
(x^(v[0]*n1[0])+x^(v[0]*(n1[0]+1))+x^(v[0]*(n1[0]+2))+...+x^(v[0]*n2[0]))
(x^(v[1]*n1[1])+x^(v[1]*(n1[1]+1))+x^(v[1]*(n1[1]+2))+...+x^(v[1]*n2[1]))
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ...............
(x^(v[K]*n1[K])+x^(v[K]*(n1[K]+1))+x^(v[K]*(n1[K]+2))+...+x^(v[K]*n2[K]))
K對應具體問題中物品的種類數。
V[i]表示該乘積表達式第 i 個因子的權重,對應具體問題的每個物品的價值或者權重
n1[i]表示該乘積表達式第 i 個因子的起始系數,對應于具體問題中的每個物品的最少個數,即最少要取多少個
n2[i]表示該乘積表達式第i個因子的終止系數,對應具體問題中的每個物品的最多個數,即最多要取多少個
解題的關鍵是要確定V,N1,N2數組的值
模板
//a為計算結果,b為中間結果 int a[MAX],b[MAX]; memset(a,0,sizeof a); a[0]=1; for(int i=1;i<=k;i++)//循環每個因子 {memset(b,0,sizeof(b));for(int j=n1[i];j<=n2[i]&&j*v[i]<=p;j++)//循環每個因子的每一項for(int k=0;k+j*v[i]<=p;k++)//循環a的每一項b[k+j*v[i]]+=a[k];//把結果加到對應位memccpy(a,b,sizeof b);//b賦值給a }p是可能的最大指數,拿鈔票組合的題來說,如果求15元有多少種組合,那么p就是15;如果問最小的不能拼出的數值,那么p就是所有錢加起來的和。
用一個last變量記錄目前最大的指數,這樣只需要在0--last上計算
改進模板
//初始化a,因為有last,所以這里無需初始化其他位 int last=0; a[0]=1; for(int i=1;i<=n;i++) {//循環每個因子int next=min(last+n2[i]*v[i],p);//計算下一次的lastfor(int j=n1[i];j<=n2[i]&&j*v[i]<=next;j++)//循環每個因子的每一項for(int k=0;k<=last&&k+j*v[i]<=next;k++)//循環a的每一項b[k+j*v[i]]+=a[k];//把結果加到對應位for(int k=0;k<maxn;k++) {//賦值a[k]=b[k]%mod;b[k]=0;}last=next;//更新last }例題
一、hdu 1085和hdu 1171兩題套用了第二個模板,省略了代碼中二三層循環里關于last2的條件(其實也可以加上)。
詳見:
hdu1085 :http://blog.csdn.net/xiaofei_it/article/details/17041467
import java.util.*; import java.math.*;public class Main {public static int maxn=8010;public static void main(String[] args) {int[] a=new int[maxn];//保存結果,a[i]表示組成i種水果的方案數為a[i]int[] b=new int[maxn];//中間結果int[] n1=new int[maxn];//第i種水果最少的個數int[] n2=new int[maxn];//第i種水果最多的個數int[] v=new int[maxn];//第i種水果的價值Scanner cin=new Scanner(System.in);//int T=cin.nextInt(); while(cin.hasNext()) {for(int i=0;i<maxn;i++) {a[i]=0;b[i]=0;n1[i]=0;n2[i]=0;}v[1]=1;v[2]=2;v[3]=5;int sum=0;for(int i=1;i<=3;i++) {n2[i]=cin.nextInt();sum+=n2[i];}if(sum==0) break;a[0]=1;int p=0;//要求最小不能拼出的錢,p就是所有錢加起來的和for(int i=1;i<=3;i++)p=p+v[i]*n2[i];for(int i=1;i<=3;i++) {for(int k=0;k<maxn;k++)b[k]=0;for(int j=n1[i];j<=n2[i]&&j*v[i]<=p;j++)for(int k=0;k+j*v[i]<=p;k++)b[k+j*v[i]]+=a[k];for(int k=0;k<maxn;k++)a[k]=b[k];}int ans=0;//遍歷找出方案數為0的錢幣組合for(int i=1;i<maxn;i++)if(a[i]==0) {ans=i;break;}System.out.println(ans);}cin.close();} }hdu 1171:http://blog.csdn.net/xiaofei_it/article/details/17041709
import java.util.*; import java.math.*;public class Main {public static int maxn=250000,maxm=60;public static void main(String[] args) {int[] a=new int[maxn];//保存結果,a[i]表示組成i種水果的方案數為a[i]int[] b=new int[maxn];//中間結果int[] n1=new int[maxm];//第i種水果最少的個數int[] n2=new int[maxm];//第i種水果最多的個數int[] v=new int[maxm];//第i種水果的價值Scanner cin=new Scanner(System.in);//int T=cin.nextInt(); while(cin.hasNext()) {for(int i=0;i<maxn;i++) {a[i]=0;b[i]=0;}for(int i=0;i<maxm;i++) {n1[i]=0;n2[i]=0;v[i]=0;}int n=cin.nextInt();if(n<0) break;int sum=0;for(int i=1;i<=n;i++) {v[i]=cin.nextInt();n2[i]=cin.nextInt();sum+=v[i]*n2[i];}int p=sum;//if(sum%2==1) p++;a[0]=1;for(int i=1;i<=n;i++) {for(int k=0;k<maxn;k++)b[k]=0;for(int j=n1[i];j<=n2[i]&&j*v[i]<=p;j++)for(int k=0;k+j*v[i]<=p;k++)b[k+j*v[i]]+=a[k];for(int k=0;k<maxn;k++)a[k]=b[k];}int ans=0;for(int i=p/2;i>=0;i--) {if(a[i]!=0) {ans=i;break;}}System.out.println(p-ans+" "+ans);}cin.close();} }二、hdu 1398套用了第一個模板,因為n2中每一項為無窮大,所以n2數組就省略了。
詳見:
hdu 1398:http://blog.csdn.net/xiaofei_it/article/details/17041815
import java.math.*; import java.util.*; public class Main {public static int maxn=300*300+10;static int[] a=new int[maxn];static int[] b=new int[maxn];static int[] v=new int[30];static void init() {for(int i=0;i<maxn;i++) {a[i]=0;b[i]=0;}for(int i=1;i<=17;i++)v[i]=i*i;a[0]=1;for(int i=1;i<=17;i++) {for(int j=0;j*v[i]<=300;j++)for(int k=0;k+j*v[i]<=300;k++)b[k+j*v[i]]+=a[k];for(int k=0;k<maxn;k++) {a[k]=b[k];b[k]=0;}}}public static void main(String[] args) {Scanner cin=new Scanner(System.in);init();while(cin.hasNext()) {int n=cin.nextInt();if(n==0) break;System.out.println(a[n]);}cin.close();} }三、hdu 2079、hdu 2082和hdu 2110三題直接套用了第二個模板。
詳見:
hdu2079
http://blog.csdn.net/xiaofei_it/article/details/17042045
import java.math.*; import java.util.*; public class Main {public static int maxn=400;static int[] a=new int[maxn];static int[] b=new int[maxn];static int[] v=new int[maxn];static int[] n1=new int[maxn];static int[] n2=new int[maxn];static void init() {for(int i=0;i<maxn;i++) {a[i]=0;b[i]=0;v[i]=0;n1[i]=0;n2[i]=0;}}public static void main(String[] args) {Scanner cin=new Scanner(System.in);int T=cin.nextInt();while(T!=0) {T--;init();int p=cin.nextInt();int n=cin.nextInt();for(int i=1;i<=n;i++) {v[i]=cin.nextInt();n2[i]=cin.nextInt();}a[0]=1;for(int i=1;i<=n;i++) {for(int j=n1[i];j<=n2[i]&&j*v[i]<=p;j++)for(int k=0;k+j*v[i]<=p;k++)b[k+j*v[i]]+=a[k];for(int k=0;k<maxn;k++) {a[k]=b[k];b[k]=0;}}System.out.println(a[p]);}cin.close();} }hdu2082:
http://blog.csdn.net/xiaofei_it/article/details/17042253
import java.util.*; import java.math.*;public class Main {public static int maxn=110;public static void main(String[] args) {int[] a=new int[maxn];//保存結果,a[i]表示組成i種水果的方案數為a[i]int[] b=new int[maxn];//中間結果int[] n1=new int[maxn];//第i種水果最少的個數int[] n2=new int[maxn];//第i種水果最多的個數int[] v=new int[maxn];//第i種水果的價值Scanner cin=new Scanner(System.in);int T=cin.nextInt(); while(T!=0) {T--;for(int i=0;i<maxn;i++) {a[i]=0;b[i]=0;n1[i]=0;n2[i]=0;v[i]=i;}for(int i=1;i<=26;i++)n2[i]=cin.nextInt();a[0]=1;int p=50;for(int i=1;i<=26;i++) {for(int k=0;k<maxn;k++)b[k]=0;for(int j=n1[i];j<=n2[i]&&j*v[i]<=p;j++)for(int k=0;k+j*v[i]<=p;k++)b[k+v[i]*j]+=a[k];for(int k=0;k<maxn;k++)a[k]=b[k];}long ans=0;for(int i=1;i<=p;i++)ans+=a[i];System.out.println(ans);}cin.close();} }hdu 2110:
http://blog.csdn.net/xiaofei_it/article/details/17042421
import java.math.*; import java.util.*; public class Main {static int mod=10000;public static int maxn=10000+7;static long[] a=new long[maxn];static long[] b=new long[maxn];static int[] v=new int[maxn];static int[] n1=new int[maxn];static int[] n2=new int[maxn];static int min(int a,int b) {return a<b?a:b; }static void init() {for(int i=0;i<maxn;i++) {a[i]=0;b[i]=0;v[i]=0;n1[i]=0;n2[i]=0;}}public static void main(String[] args) {Scanner cin=new Scanner(System.in);//int T=cin.nextInt();while(cin.hasNext()) {init();int n=cin.nextInt();if(n==0) break;int p=0;for(int i=1;i<=n;i++) {v[i]=cin.nextInt();n2[i]=cin.nextInt();p+=v[i]*n2[i];}if(p%3!=0) {System.out.println("sorry");continue;}p/=3;int last=0;a[0]=1;for(int i=1;i<=n;i++) {int next=min(last+n2[i]*v[i],p);//計算下一次的lastfor(int j=n1[i];j<=n2[i]&&j*v[i]<=next;j++)for(int k=0;k<=last&&k+j*v[i]<=next;k++)b[k+j*v[i]]+=a[k];for(int k=0;k<maxn;k++) {a[k]=b[k]%mod;b[k]=0;}last=next;//更新last}if(a[p]!=0)System.out.println(a[p]%mod);elseSystem.out.println("sorry");}cin.close();} }另外,至于什么時候用第一個模板,什么時候用第二個模板,就看題目規模。 通常情況下,第一個模板就夠用了,上面的那些用第二個模板的題目用第一個模板同樣能AC。 但如果數據規模比較大(通常不會有這種情況),就要使用第二個模板了。 以上題目n1均為0。
四、hdu 2152是一道n1不為0的題目,我在這里分別套用第一個和第二個模板解題。
詳見: hdu 2152:http://blog.csdn.net/xiaofei_it/article/details/17042497
C++
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=130; int a[maxn],b[maxn]; int n1[maxn],n2[maxn]; int v[maxn]; void init() {memset(a,0,sizeof a);memset(b,0,sizeof b);memset(n1,0,sizeof n1);memset(n2,0,sizeof n2); } int main() {for(int i=0;i<maxn;i++)v[i]=1;int n,m;while(scanf("%d%d",&n,&m)!=EOF){init();for(int i=1;i<=n;i++)scanf("%d%d",&n1[i],&n2[i]);a[0]=1;for(int i=1;i<=n;i++){memset(b,0,sizeof b);for(int j=n1[i];j<=n2[i]&&j*v[i]<=m;j++)for(int k=0;k+j*v[i]<=m;k++)b[k+v[i]*j]+=a[k];//memcpy(a,b,sizeof b);for(int k=0;k<maxn;k++)a[k]=b[k];}printf("%d\n",a[m]);} }Java
import java.util.*; import java.math.*;public class Main {public static int maxn=110;public static void main(String[] args) {int[] a=new int[maxn];//保存結果,a[i]表示組成i種水果的方案數為a[i]int[] b=new int[maxn];//中間結果int[] n1=new int[maxn];//第i種水果最少的個數int[] n2=new int[maxn];//第i種水果最多的個數int[] v=new int[maxn];//第i種水果的價值Scanner cin=new Scanner(System.in);//int T=cin.nextInt(); while(cin.hasNext()) {for(int i=0;i<maxn;i++) {a[i]=0;b[i]=0;n1[i]=0;n2[i]=0;v[i]=1;}int n=cin.nextInt();int m=cin.nextInt();for(int i=1;i<=n;i++) {n1[i]=cin.nextInt();n2[i]=cin.nextInt();}a[0]=1;for(int i=1;i<=n;i++) {//循環每一個因子for(int k=0;k<maxn;k++)b[k]=0;for(int j=n1[i];j<=n2[i]&&j*v[i]<=m;j++)//循環每個因子的每一項for(int k=0;k+j*v[i]<=m;k++)//循環a的每一項b[k+j*v[i]]+=a[k];//把結果加到對應的位for(int k=0;k<maxn;k++)//b賦值給aa[k]=b[k];}System.out.println(a[m]);}cin.close();} }?
總結
- 上一篇: HDU2049 组合数学 错排公式
- 下一篇: HDU2515 Yanghee 的算术