结合 ACdreamer 以及 zhoufenqin 的博客终于弄懂了如何用生成函数解一类特定的大背包问题。
首先要介绍一些重要的公式、数列、函数
暂时不管五边形数的实际意义是什么……
前几项:1, 5, 12, 22, 35…
其通项: ai=3n2−n2 a i = 3 n 2 − n 2
然而我们需要的是广义五边形数
具体就是取 a0,a1,a−1,a2,a−2,a3,a−3... a 0 , a 1 , a − 1 , a 2 , a − 2 , a 3 , a − 3 . . . 构成新的一个数列
前几项:0, 1, 2, 5, 7, 12, 15, 22, 26, 35…(0可能是不需要的具体看情况)
百度百科居然只有一个数论欧拉函数……我还在想这怎么展开
此处的欧拉函数是重新定义的
分拆数的实际意义还是需要理解的:
分拆是指将一个正整数表示成不大于其自身的一个或几个正整数的无序和,分拆数(partition number)则指不同的分拆方式的数目。
将其定义为 p(1),p(2)... p ( 1 ) , p ( 2 ) . . . 其中 p(4)=5 p ( 4 ) = 5
小的分拆数是可以用完全背包在 O(n2) O ( n 2 ) 内求出来的
接下来介绍用生成函数的思路求得分拆数的方法
由生成函数的基础知识可以知道分拆数数列的生成函数可以表示为
P(x)=(1+x+x2+x3+...)(1+x2+x4+x6+...)(1+x3+x6+x9+...)... P ( x ) = ( 1 + x + x 2 + x 3 + . . . ) ( 1 + x 2 + x 4 + x 6 + . . . ) ( 1 + x 3 + x 6 + x 9 + . . . ) . . .
关键是 之前介绍的欧拉函数的倒数就是这个分拆函数
我们可以得到如下结论:
1. 易知 p(0)=1 p ( 0 ) = 1
2. φ(x)P(x) φ ( x ) P ( x ) 中只有常数项,也就是剩下无论是 x x 的几次,其系数为0
现在我们举例说明如何求 p(8) p ( 8 ) ,为了防止思维混乱,建议特别注意一下博文中
P的大小写
φ(x)P(x) φ ( x ) P ( x ) 中关于 x8 x 8 的项有
p(8)x8、(−x1)(p(7)x7)、(−x2)(p(6)x6)、(x5)(p(3)x3)、(x7)(p(1)x1) p ( 8 ) x 8 、 ( − x 1 ) ( p ( 7 ) x 7 ) 、 ( − x 2 ) ( p ( 6 ) x 6 ) 、 ( x 5 ) ( p ( 3 ) x 3 ) 、 ( x 7 ) ( p ( 1 ) x 1 )
因此 p(8)−p(7)−p(6)+p(3)+p(1)=0 p ( 8 ) − p ( 7 ) − p ( 6 ) + p ( 3 ) + p ( 1 ) = 0
由此我们可以递推求出 p(n) p ( n )
递推式为 p(n)=p(n−1)+p(n−2)−p(n−5)−p(n−7)+... p ( n ) = p ( n − 1 ) + p ( n − 2 ) − p ( n − 5 ) − p ( n − 7 ) + . . .
直到下标小于0
由五边形数的通项可知总体复杂度为 O(nn−−√) O ( n n )
当然题目没有这么裸,这里还要写一下HDU4658的题解
题意:要求拆分的数中每个数出现的次数不能大于等于k次
生成函数走起
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 2 n 的背包,你有体积为 1到n 1 到 n 的物品,且体积为 i i 的物品有 ai a i 个,求每种体积的装箱方案数
数据特点:以装箱问题的方式做这题的话, 1−E(n−−√级别) 1 − E ( n 级 别 ) 体积的问题应做多重背包,剩下的应做完全背包
分析:这题是两种限制分拆的糅合①物品不是无限量的,甚至每种物品的数量各不相同②你只有1-n的物品,而不是1-2n的(如果是1-2n的,那可以直接认为是1- ∞ ∞ 的)
公式推导:
摘录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;