(前排出售零食瓜子)
前言:
母函式是個很難的東西,難在數學
而ACM中所用的母函式只是母函式的基礎
應該說除了不好理解外,其他都是非常簡單的
母函式即生成函式,是組合數學中尤其是計數方面的一個重要理論和工具。
但是ACM中的母函式木有像數學那麼深究,應用的都是母函式的一些基本
(就好比方程的配方,因式的分解,寫起來容易,你用電腦寫起來就麻煩了,所以學計算機就不要老跟數學家瞎鬧( ̄3 ̄))
什麼是母函式
就是把一個已知的序列和x的多項式合併起來,新產生的多項式就叫原來序列的母函式
至於怎麼合併,看這個例子
序列{0,1,2,3,4,5...n}的母函式就是
f(x)=0+x+2x^2+3x^3+4x^4+...+nx^n(這個x沒有任何意義,應該說,你不需要把它當做一個函式,你只要知道母函式這麼寫就可以了)
序列{1,1,1,1,1......}的母函式就是
f(x)=1+x+x^2+x^3+x^4....
二項式展開的序列比如這個{1,4,6,4,1,0,0,0,0,0.....}是C(4,0)到C(4,4)的係數,那它的母函式就是
f(x)=1+4x+6x^2+4x^3+1x^4
母函式就長這樣,對正常人來講,這種東西毫無意義( ° △ °|||)
那看點有意義的東西(以下都是經典題型,我從杭電ACM課件抄來的)
有1克、2克、3克、4克的砝碼各一枚,能稱出哪幾種重量?每種重量各有幾種可能方案?
假如x的冪次數表示幾克的砝碼
那麼
1克的砝碼錶示為1+x^1
2克的砝碼錶示為1+x^2
3克的砝碼錶示為1+x^3
4克的砝碼錶示為1+x^4
每個砝碼都可以選擇取或不取
所以這裡的1可以認為1*x^0,表示不取這顆砝碼
那麼把這些乘起來
(1+x^1)(1+x^2)(1+x^3)(1+x^4)
=1+(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)
根據指數來看,我們可以稱出0~10這麼多的重量,其中3~7的係數為2,說明有2種稱的方法
那麼我們來細看一遍
0:(什麼砝碼都不放).......................(1種)
1:1.............................................(1種)
2:2.............................................(1種)
3:3或1+2.....................................(2種)
4:4或1+3.....................................(2種)
5:1+4或2+3.................................(2種)
6:2+4或1+2+3..............................(2種)
7:3+4或1+2+4..............................(2種)
8:1+3+4......................................(1種)
9:2+3+4......................................(1種)
10:1+2+3+4.................................(1種)
分毫不差(・ˍ・*)
所以說母函式在ACM就是這麼用的,跟函式沒關係,跟寫法有關係。。。
再來一題
求用1分、2分、3分的郵票貼出不同數值的方案數:(每張郵票的數量是無限的)
那麼
1分:(1+x^1+x^2+x^3+x^4+......)
2分:(1+x^2+x^4+x^6+x^8+......)
3分:(1+x^3+x^6+x^9+x^12+......)
然後這3個乘起來(讓電腦去乘吧)
對於這種無限的,題目肯定會給你他詢問的數值的範圍,計算到最大的範圍就可以了
附程式碼:
#include<cstdio>
typedef long long LL;
const int N = + ;//假如題目只問到100為止
const int MAX = ;//題目只有1,2,3這3種郵票
LL c1[N], c2[N];//c2是臨時合併的多項式,c1是最終合併的多項式
int n;
void init(){
c1[] = ;//一開始0的情況算一種
for(int i = ; i <= MAX; i ++){//把1分到MAXN的郵票合併,變成一個多項式
for(int j = ; j < N; j += i){//i分的郵票,步長是i
for(int k = ; j + k < N; k ++){//從x^0到x^N遍歷一遍
c2[j + k] += c1[k];//因為j的所有項係數為1,所以c1[k]可以看成c1[k]*1;
}
}
for(int j = ; j < N; j ++){//把c2的資料抄到c1,清空c2
c1[j] = c2[j];
c2[j] = ;
}
}
}
int main(){
init();
while(scanf("%d", &n) != EOF){
printf("%I64d\n", c1[n]);
}
}
我們就來把這個模板用於實際吧
hdu 1028
http://acm.hdu.edu.cn/showproblem.php?pid=1028
題目問一個數字n能夠拆成多少種數字的和
比如n=4
4 = 4;
4 = 3 + 1;
4 = 2 + 2;
4 = 2 + 1 + 1;
4 = 1 + 1 + 1 + 1;
有5種,那麼答案就是5
AC程式碼:
#include<cstdio>
typedef long long LL;
const int N = + ;
const int MAX = + ;
LL c1[N], c2[N];
int n;
void init(){
c1[] = ;
for(int i = ; i <= MAX; i ++){
for(int j = ; j < N; j += i){
for(int k = ; j + k < N; k ++){
c2[j + k] += c1[k];
}
}
for(int j = ; j < N; j ++){
c1[j] = c2[j];
c2[j] = ;
}
}
}
int main(){
init();
while(scanf("%d", &n) != EOF){
printf("%I64d\n", c1[n]);
}
}
再來,hdu 1398
http://acm.hdu.edu.cn/showproblem.php?pid=1398
題目說一個國家的硬幣都是方形的,面值也是方形的
有1塊錢,4塊錢,9塊錢,16塊錢......一直到289塊錢(17^2)
問想組成n塊錢有幾種方法
AC程式碼:
#include<cstdio>
typedef long long LL;
const int N = + ;
const int MAX = ;
LL c1[N], c2[N];
int n;
void init(){
c1[] = ;
for(int i = ; i <= MAX; i ++){
for(int j = ; j < N; j += i*i){
for(int k = ; j + k < N; k ++){
c2[j + k] += c1[k];
}
}
for(int j = ; j < N; j ++){
c1[j] = c2[j];
c2[j] = ;
}
}
}
int main(){
init();
while(scanf("%d", &n) != EOF && n){
printf("%I64d\n", c1[n]);
}
}
都是改一些小地方,都是模板題(o゚ω゚o)
最後一道
hdu 1085
http://acm.hdu.edu.cn/showproblem.php?pid=1085
AC程式碼:
#include<cstdio>
#include<cstring>
typedef long long LL;
const int N = * (++) + ;
int cost[] = {, , };
LL c1[N], c2[N];
int num[];
int MAX;
int main(){
while(~scanf("%d%d%d", &num[], &num[], &num[])){
if(num[] == && num[] == && num[] == ) break;
memset(c1, , sizeof(c1));
memset(c2, , sizeof(c2));
MAX = num[] + num[] * + num[] * ;//計算最大值
c1[] = ;
for(int i = ; i < ; i ++){
for(int j = ; j <= num[i] * cost[i]; j += cost[i]){
for(int k = ; j + k <= MAX; k ++){
c2[j + k] += c1[k];
}
}
for(int j = ; j < N; j ++){
c1[j] = c2[j];
c2[j] = ;
}
}
for(int i = ; i <= MAX + ; i ++){
if(!c1[i]){
printf("%d\n", i);
break;
}
}
}
}
母函式在數學上真的用處很大,但是我沒怎麼看到在ACM上有什麼太大的用處(可能我做的題還不夠多 T_T)
比如剛剛上面的3個例題,都有更快的做法
第一題:動態規劃,時間複雜度O(n^2)
#include<cstdio>
const int N = + ;
int dp[N];
int n;
void init(){
dp[] = ;
for(int i = ; i < N; i ++){
for(int j = i; j < N; j ++){
dp[j] += dp[j - i];
}
}
}
int main(){
init();
while(scanf("%d", &n) != EOF){
printf("%d\n", dp[n]);
}
}
第二題:動態規劃,時間複雜度O(n^2)
#include<cstdio>
const int N = + ;
int dp[N];
int n;
void init(){
dp[] = ;
for(int i = ; i <= ; i ++){
for(int j = i*i; j < N; j ++){
dp[j] += dp[j - i*i];
}
}
}
int main(){
init();
while(scanf("%d", &n) != EOF && n){
printf("%d\n", dp[n]);
}
}
第三題:≖‿≖✧特判就好了,時間複雜度O(1)
#include<cstdio>
int a, b, c;
int ans;
int main(){
while(~scanf("%d%d%d", &a, &b, &c) && (a || b || c)){
if(a >= || a >= && b >= || a >= && b >= ) ans = a + *b + *c + ;
else if(a == ) ans = ;
else ans = a + *b + ;
printf("%d\n", ans);
}
}
哈哈哈有沒有被騙的感覺,有些題目,不要陷進演算法裡,這題O(1)的複雜度就可以了,如果你用三個for迴圈,那就太慢了,而且數量不同,還沒有辦法預處理,如果資料量大,肯定超時
所以,母函式我們只要理解原理就好了
那麼ACM的母函式講完了(*°∀°)
之後是數學上的母函式,不想看的人就可以結束本章內容了(*°∀°)