生成函数

回顾了以前的一些没有完成的题目,发现自己的知识漏洞还挺多的,比如这个生成函数【以前用多重背包一直WA】

生成函数干嘛的?

比较功利的来说,就是用来求解各种排列组合的问题。如果是求组合问题,那就是构造普通型生成函数,如果是求排列的问题,那就是构造指数型生成函数。

最关键的是,这个不像DP,这个是有固定的模板的~那就很简单了。以下是我找的一些生成函数的题目,基本都是一个思想,主要就是通过例子来熟练使用生成函数

普通型生成函数

假设你手里有1张1块,1张2块,1张5块的纸币,能够组成多少种数量的钱,每种数量的钱有几种组成方案

1块 -> 金额1
2块 -> 金额2
5块 -> 金额5
1块 + 2块 -> 金额3
1块 + 5块 -> 金额6
2块 + 5块 -> 金额7
1块 + 2块 + 5块 -> 金额8
(省略了一张都不用,金额0的情况)

设有函数:G(x) = a_0x^0 + a_1x^1 + a_2x^2 + …… + a_nx^n

其中,x2表示金额2的情况,x5表示金额5的情况,而前面的系数a2表示金额2的组成方案数量,a5表示金额5的组成方案数量

所以刚才举例中可得到的函数是:
G(x) = x^0 + x^1 + x^2 + x^3 + x^5 + x^6 + x^7 + x^8

把这个函数分解开可以得到

  • 只有一张1块的生成函数 G(x) = x0 + x1 【可以有0块和1块这两种金额】
  • 只有一张2块的生成函数 G(x) = x0 + x2

把这两个式子相乘可以得到1块和2块的生成函数:
(x^0 + x^1)×(x^0 + x^2) = x^0 + x^1 + x^2 + x^3

  • 1块和2块的生成函数 G(x) = x0 + x1 + x2 + x3
  • 只有一张5块的生成函数 G(x) = x0 + x5

把这两个式子相乘可以得到1块、2块和5块的生成函数:
(x^0 + x^1 + x^2 + x^3) × (x^0 + x^5) = x^0 + x^1 + x^2 + x^3 + x^5 + x^6 + x^7 + x^8

组合问题其实就是与非运算的加法问题。比如上述方案中,就是1、2、5都要;1、2要,5不要;1、5要,2不要;……构成的方案,而这种运算方式恰好可以与幂级数的乘积对应起来

HDU2082

26个字母价值各不相同,且每个字母的数量不一,最后问能组成多少种总价值为50的单词的方案

在上面的例子中,只用到了一张1块,1张2块和1张5块。假设有2张2块,那么就应该能组成0、2、4三种金额;假如有3张2块,那么就能组成0,2,4,6四种金额。也就是说,3张2块的生成函数为:
x^0 + x^2 + x^4 + x^6

如果数量不限,那么无数张2块的生成函数为:
x^0 + x^2 + x^4 + …… + x^{2n}

在这个基础之上,这类题目其实就变成了多项式的乘积问题。在实际编码时,多项式用数组表示,数组的下标就是对应的多项式的次数。

首先初始化三个数组,a数组记为目前计算完成的生成函数,b数组是当前正在计算的生成函数,将a数组和b数组进行多项式的乘积操作,将计算的答案存储到c数组中,最后将c数组拷贝到a数组中,更新生成函数,具体的编码过程如下:

memset(a,0,sizeof(a));
memset(c,0,sizeof(c));
a[0] = 1;
for (int k=1; k<=26; k++) {
    memset(b,0,sizeof(b));
    cin>>num;
    if (num==0) continue;
    for (int i=0; i<=num && k*i<=50; i++)
        b[i*k] = 1; //当前价值的生成函数,对应倍数的系数置1
    for (int i=0; i<=50; i++) 
        for (int j=0; j<=50 && i+j<=50; j++)
            c[i+j] += a[i]*b[j];  //i+j表示幂指数相加,a[i]*b[j]表示系数相乘
    for (int i=0; i<=50; i++) {  //拷贝c数组
        a[i] = c[i];
        c[i] = 0;
    }
}

具体的思路就是不断累乘多项式,每个多项式就是每个价值的生成函数,最后得到的就是所有价值的生成函数,也就是所有组成的方案数量,上面题目中,x50的系数就是对应的方案数,也就是a[50]的值

简化累乘过程

上述代码中用到了三个数组,其实可以只用两个数组,因为每一个新的生成函数,系数都是1,比如 4个价值为2 的字母的生成函数:
G(x) = x^0 + x^2 + x^4 + x^6 + x^8
而相乘的过程就是x0乘以数组a中的每一项,x2乘以数组a中的每一项……所以可以得到如下的简化代码

for (int i=0; i<=num && k*i<=50; i++)
    //当前指数是i*k,系数是1
    for (int j=0; j<=50 && i*k+j<=50; j++)
        c[i*k+j] += a[j];

其中i*k就是当前价值的生成函数的指数,当字母价值为2时,k=2,i从0到4,也就对应了当前每一项的指数,对于每一项指数都需要乘以已经计算好的a数组中的每一项,而a数组中的每一项的系数就是a[j],指数是j,所以i*k+j就是新的指数,而相应的系数,因为当前系数是1,所以相当于加上a[j]

最后需要求的是小于等于价值为50的单词的方案总数,那就是a[1]累加到a[50]即可,也就是系数和相加

HDU2110

这里的总数不再是50这个固定的值了,而是所有给出的量的总和除以3,也就是1/3的总资产。

核心代码还是一样,只是替换了部分变量而已。下满的p[i]存储的是每个资产的价值,m[i]存储的每个资产的数量,因为上题中资产的价值是固定的,所以这题需要增加数组存储每个资产的价值和数量

for (int k=1; k<=n; k++) {
    for (int i=0; i<=m[k] && p[k]*i<=total; i++) {
        for (int j=0; j<=total && i*p[k]+j<=total; j++) {
            b[i*p[k]+j] += a[j];
            b[i*p[k]+j] %= 10000;
        }
     }
     for (int i=0; i<=total; i++) {
         a[i] = b[i];
         b[i] = 0;
     }
}
HDU1028

这题和上面两题不同的是,在这题中,每一个价值的数量是无限的,有无限个1、无限个2……而循环的终点就是目标价值量N

核心代码与上面两题基本相同

for (int k=1; k<=N; k++) {
    for (int i=0; k*i<=N; i++)
        for (int j=0; j<=N && i*k+j<=N; j++)
            b[i*k+j] += a[j];
    for (int i=0; i<=N; i++) {
        a[i] = b[i];
        b[i] = 0;
    }
}
HDU1171

这题是大一的时候用多重背包做了很久,一直没做出来,最后就放着的一题。现在去看看当初写的代码量,都快到200行了。

在这题中,目标价值不是固定的了。题意是尽可能的将所有资产平分,那也就是说,目标价值是尽量接近总价值的一半,且存在可能的方案(系数不为0)

核心代码与HDU2110一样

for (int k=1; k<=N; k++) {
    for (int i=0; i<=m[k] && i*v[k]<=total; i++)
        for (int j=0; j<=total && i*v[k]+j<=total; j++)
            b[i*v[k]+j] += a[j];
    for (int i=0; i<=total; i++) {
        a[i] = b[i];
        b[i] = 0;
    }
}

在计算完成之后,从中点向起点遍历,找到第一个系数不为0的指数即可,因为存在只有一种资产的情况,所以遍历时不能只遍历到1,而需要遍历到0,也就是说一方什么都没分到,另一方拿走全部

int t = total/2;
for (int i=t; i>=0; i--) {  //注意这里是大于等于0,而不是大于0
    if (a[i]!=0) {
        cout<<total-i<<" "<<i<<endl;
        break;
    }
}
HDU2152

在这题中,加上了每个价值量(水果)最少出现的次数和最多出现的次数,且题意中总数不大于100

因为第二个循环代表的是当前价值量的使用次数,所以修改第二层循环即可,另外,由于在题意中,每个水果的价值量单位都是1,所以前面几题中的p[k]、v[k]都可以省略

for (int k = 1; k<=N; k++) {
    for (int i = mins[k]; i<=maxs[k] && i<=100; i++)
        for (int j = 0; j<=100 && i+j<=100; j++)
            b[i+j] += a[j];
    for (int i=0; i<=100; i++) {
        a[i] = b[i];
        b[i] = 0;
    }
}

以上就是普通型生成函数的例题,通过这几题,大概能得到一个通用的组合方案数计算的模板,理解之后,修改代码就方便了

指数型生成函数

上面已经提到,指数型生成函数是用来处理排列问题的。排列问题存在重复的排列项,比如11333,有2个1和3和3,就需要除以重复项2!3!,所以得到指数型的生成函数:

G(x) = a_0 + \frac{a_1}{1!}x + \frac{a_2}{2!}x^2 + \frac{a_3}{3!}x^3 + …… + \frac{a_n}{n!}x^n

假设有3个红球,2个黄球,4个绿球。

那么我们规定3个红球的生成函数是:
G(x) = 1 + \frac{x}{1!} + \frac{x^2}{2!} + \frac{x^3}{3!}

2个黄球的生成函数是:
G(x) = 1 + \frac{x}{1!} + \frac{x^2}{2!}

3个红球和2个黄球的生成函数是:
(1 + \frac{x}{1!} + \frac{x^2}{2!} + \frac{x^3}{3!})×(1 + \frac{x}{1!} + \frac{x^2}{2!})

4个绿球的生成函数是:
G(x) = 1 + \frac{x}{1!} + \frac{x^2}{2!} + \frac{x^3}{3!} + \frac{x^4}{4!}

所以:3个红球、2个黄球、4个绿球的生成函数为:
(1 + \frac{x}{1!} + \frac{x^2}{2!} + \frac{x^3}{3!})×(1 + \frac{x}{1!} + \frac{x^2}{2!}) × (1 + \frac{x}{1!} + \frac{x^2}{2!} + \frac{x^3}{3!} + \frac{x^4}{4!}) =

1 + 3\frac{x^1}{1!} + 9\frac{x^2}{2!} + 26\frac{x^3}{3!} + 71\frac{x^4}{4!} + 180\frac{x^5}{5!} + 410\frac{x^6}{6!} + 805\frac{x^7}{7!} + 1260\frac{x^8}{8!} + 1135\frac{x^9}{9!}

也就是说,从这些球中,取2个的排列数为9,取3个的排列数为26,去4个的排列数为71……

非固定数量的球的生成函数
  • 无限数量
    G(x) = 1 + \frac{x}{1!} + \frac{x^2}{2!} + \frac{x^3}{3!} + … + \frac{x^n}{n!} = e^x
  • 奇数个
    G(x) = \frac{x}{1!} + \frac{x^3}{3!} + \frac{x^5}{5!} + … + \frac{x^{2n+1}}{(2n+1)!} = \frac{e^x - e^{-x}}{2}
  • 偶数个
    G(x) = 1 + \frac{x^2}{2!} + \frac{x^4}{4!} + … + \frac{x^{2n}}{2n!} = \frac{e^x + e^{-x}}{2}

具体计算时,按固定数量的一样,相乘即可,非固定数量可以直接用级数和来代替

HDU1521

这是固定数量的球,我们同样使用数组来表示多项式,数组下标对应的是指数,数组中的值对应的是系数

double Factorial(int n) { //计算n!
    double ans = 1;
    for (int i=1; i<=n; i++) ans*=i;
    return ans;
}
int main() {
    double a[11],b[11];
    int n,m,num[11];
    while (cin>>n>>m) {
        for (int i=1; i<=n; i++)
            cin>>num[i];
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        a[0] = 1.0;  //初始化数组为1*x^0 = 1
        for (int k=1; k<=n; k++) {  //计算第k个数
            for (int i=0; i<=num[k]; i++)  //当前计算的数的指数遍历
                for (int j=0; j<=m; j++) //对于每一个指数,都分别乘以已经计算好的多项式
                    b[j+i] += a[j]/Factorial(i);  //每一个项初始化的都是x^n/n!,计算时,相当于指数相加,系数除以n!
            for(int j=0;j<=m;j++){
                a[j] = b[j];
                b[j] = 0;
            }
        }
        cout<<fixed<<setprecision(0)<<a[m]*Factorial(m)<<endl; //保证无小数输出
    }
    return 0;
}

我们发现核心代码还是相同的,由于数组没项存储的还是指数对应的系数,而指数是1/n!,所以计算时存在小数,所以用double类型

核心代码中的计算b[j+i] += a[j]/Factorial(i); ,由于刚初始化时的每一个数的生成函数的每一项的系数都是1/i! (i表示的是当前的指数),所以多项式相乘就相当于指数相加,系数等于原系数除以i!

大致上与普通型生成函数相比,核心代码还是一致的,只是计算时多出了计算阶乘的步骤

HDU2065

这题的与上题相比不同的是,数字(字母)有无限个,出现的次数不限或者限奇数次、偶数次

根据上面的公式,得到无限次的生成函数和偶数次的生成函数分别是:
e^x\frac{e^x + e^{-x}}{2}

而题意中,无限次的有两个字母,偶数次的也有两个字母,所以生成函数为:
e^x × e^x × \frac{e^x + e^{-x}}{2} × \frac{e^x + e^{-x}}{2} = \frac{e^{4x}+2e^{2x}+1}{4}

现在我们需要求的是排列的个数是n的时候的方案数量,也就是\frac{x^n}{n!}的系数是多少

我们把e4x和e2x在x=0处进行泰勒展开,变为多项式【总算用到高等数学中的东西了】。展开式就不累赘了,展开后,e4x\frac{x^n}{n!}的系数为4n;e2x\frac{x^n}{n!}的系数为2n

所以,所求的系数为\frac{4^n + 2^{n+1}}{4} = 4^{n-1} + 2^{n-1}

所以只要将n带入这个式子即可,当然由于题中的n很大,所以要用到快速幂,快速幂在前面的文章中已经有介绍了,矩阵快速幂——入门

所以最后的答案就是(quickPow(4,n-1)+quickPow(2,n-1))%100

POJ3734和上题相同,题意表述不同而已

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容