生成函数-背包问题学习笔记

来源:互联网 发布:苹果手机恢复软件 编辑:程序博客网 时间:2024/05/16 15:29

结合 ACdreamer 以及 zhoufenqin 的博客终于弄懂了如何用生成函数解一类特定的大背包问题。

首先要介绍一些重要的公式、数列、函数

xn1=(x1)(1+x+x2+x3+...+xn1)

由于给你一个生成函数G(x),我们需要的只是每一个xi的系数,所以不会去求一个比方说G(5)这样的数出来,因此参数x的值可以任意确定。当其趋近于0,n可以无限大时有
11x=1+x+x2+x3+...

五边形数:

暂时不管五边形数的实际意义是什么……
前几项:1, 5, 12, 22, 35…
其通项:ai=3n2n2

然而我们需要的是广义五边形数
具体就是取 a0,a1,a1,a2,a2,a3,a3...构成新的一个数列

前几项:0, 1, 2, 5, 7, 12, 15, 22, 26, 35…(0可能是不需要的具体看情况)

欧拉函数:

百度百科居然只有一个数论欧拉函数……我还在想这怎么展开
此处的欧拉函数是重新定义的

φ(x)=k=1(1xk)

也暂时不管欧拉函数的实际意义是什么
然后假如我们将无穷项全部乘开,会有欧拉函数展开式:
φ(x)=1xx2+x5+x7x12x15+...=k=(1)k xk(3k1)2=1+k=1(1)k(xk(3k1)2+xk(3k+1)2)

将欧拉函数完全展开后x的指数就是广义五边形数,系数则是1,-1

分拆数:

分拆数的实际意义还是需要理解的:

分拆是指将一个正整数表示成不大于其自身的一个或几个正整数的无序和,分拆数(partition number)则指不同的分拆方式的数目。

将其定义为p(1),p(2)...其中p(4)=5

4=1+1+1+14=1+1+24=1+34=2+24=4

小的分拆数是可以用完全背包在O(n2)内求出来的
接下来介绍用生成函数的思路求得分拆数的方法
由生成函数的基础知识可以知道分拆数数列的生成函数可以表示为
P(x)=(1+x+x2+x3+...)(1+x2+x4+x6+...)(1+x3+x6+x9+...)...

关键是 之前介绍的欧拉函数的倒数就是这个分拆函数

1φ(x)=k=111xk=k=1(1+(xk)1+(xk)2+...)=P(x)

接下来就是P(x)系数的计算
φ(x)P(x)=(1x1x2+x5+x7x12x15+...)(p(0)x0+p(1)x1+p(2)x2+p(3)x3+...)=1

我们可以得到如下结论:
1. 易知p(0)=1
2. φ(x)P(x)中只有常数项,也就是剩下无论是x的几次,其系数为0

现在我们举例说明如何求p(8),为了防止思维混乱,建议特别注意一下博文中
P的大小写

φ(x)P(x)中关于x8的项有
p(8)x8(x1)(p(7)x7)(x2)(p(6)x6)(x5)(p(3)x3)(x7)(p(1)x1)
因此p(8)p(7)p(6)+p(3)+p(1)=0
由此我们可以递推求出p(n)
递推式为p(n)=p(n1)+p(n2)p(n5)p(n7)+...
直到下标小于0
由五边形数的通项可知总体复杂度为O(nn)

当然题目没有这么裸,这里还要写一下HDU4658的题解
题意:要求拆分的数中每个数出现的次数不能大于等于k次

生成函数走起

G(x)=i=1(1+xi+x2i+...xi(k1))=i=11xik1xi=φ(xk)φ(x)=φ(xk)P(x)

φ(xk)=1xkx2k+x5k+x7k...

将两个函数乘开后对xn的系数求下和即可,复杂度根号级别的
代码:

ll five[maxn], p[maxn], cnt;//five是广义五边形数 int cal(int x){    return (3*x*x-x) / 2;}void init(){    /*        此处广义五边形数中那个0并没有求进来,需要注意一下     */     int x = 1;    for (cnt = 0; ; ++cnt){        five[cnt] = cal(x);        if (five[cnt] > maxn) break;        x = x <= 0 ? -x + 1 : -x;    }    p[0] = 1;    for (int i = 1; i < maxn; ++i){        for (int j = 0; i - five[j] >= 0; ++j){            if (j % 4 < 2) p[i] = (p[i] + p[i-five[j]]) % MOD;            else p[i] = rule(p[i] - p[i-five[j]]);        }    }} int n, m;int main(){    int ik, i, j, k, kase;    init();    scanf("%d", &kase);    while(kase--){        scanf("%d%d", &n, &m);        ll ans = p[n];        int sign = -1;        for (int i = 0; ; ++i){            if (m * five[i] > n) break;             ans = rule(ans + sign*p[n-m*five[i]]);            if (i & 1) sign = -sign;        }        printf("%lld\n", ans);    }    return 0;}

HDU 6042
题意: 一个体积为2n的背包,你有体积为1n的物品,且体积为i的物品有ai个,求每种体积的装箱方案数
数据特点:以装箱问题的方式做这题的话,1E(n)体积的问题应做多重背包,剩下的应做完全背包
分析:这题是两种限制分拆的糅合①物品不是无限量的,甚至每种物品的数量各不相同②你只有1-n的物品,而不是1-2n的(如果是1-2n的,那可以直接认为是1-的)
公式推导:

G(x)=i=1n(1+xi+x2i+...+xaii)=i=1n1xi(ai+1)1xi

到了这一部分母很好解决,因为我们只关心x指数小于等于2n的项,所以分母只有n项是有用的。然后因为限制②分子我们不能直接当做是分拆数生成函数
i=1n11xi=i=111xii=n+111xi=P(x)i=n+1(1xi)1xn+1xn+2...x2n

最后那个同余等于是因为我们只考虑指数小于等于2n的项
因为最后x的指数是连续的,所以可以用前缀和处理


摘录zhoufenqin博客中一些特别的分拆数:

限制分拆
给一些分拆加限制条件。例如8的分拆有22种,
其中分拆的数中全部都是奇数的有6种:7+1, 5+3, 5+1+1+1, 3+3+1+1, 3+1+1+1+1+1, 1+1+1+1+1+1+1+1;
同样,若要求8分拆的数中是两两不同的也有6种:8, 7+1, 6+2, 5+3, 5+2+1, 4+3+1
PS:这个就是4658中K=1的情况
已证明一个数的分拆中满足以上两种条件的个数是相同的,详见http://en.wikipedia.org/wiki/Glaisher%27s_theorem
一些有关限制分拆的结论:
·n的分拆数中最大部分为m的个数=把n分拆成m部分的个数

如图,左边最大部分m=3,等于把n拆成3部分(右图) 把图转置即可
·n的分拆数中每一部分都小于等于m的个数=把n分成m份或更小
·n的分拆数中每部分的数都相等的个数=n 的因子个数
Eg. 6=2+2+2, 6=3+3,6=1+1+1+1+1+1
·n的分拆数中每部分都是1或2(或者把n分拆成1或2部分)的个数=floor(n/2+1);
Eg. 6=1+1+1+1+1+1, 6=1+1+1+1+2, 6=1+1+2+2, 6=2+2+2
·n的分拆数中每部分都是1或2或3(或者把n分拆成1或2或3部分)的个数=(n+3)^2/12;