完全背包(SOJ 3531)
SOJ 3531: Number Pyramids http://acm.scu.edu.cn/soj/problem.action?id=3531
题意:首先介绍一个概念,由正整数构成的金字塔,从顶层往下依次是第1层,第2层,......,并且满足第i层有i个数。定义numer[i][j]表示第i层的第j个数,那么numer[i][j]=numer[i+1][j]+numer[i+1][j+1].
下面是一个例子:
15
6 9
3 3 6
2 1 2 4
现在给出金字塔的层数n和顶层的数top,求解这样的金字塔有多少个。
分析:上述的金字塔完全由最底层的n个数决定,定义为num[i], 0≤i<n. 若这n个数确定,则该金字塔唯一确定。以4层金字塔为例,其顶层数为
num[0]+3*num[1]+3*num[2]+num[3],
5层金字塔的顶层数为
num[0]+4*num[1]+6*num[2]+4*num[3]+num[4].
总结规律之后发现给出num[i], 0≤i<n,顶层数
top=C(n-1,0)*num[0]+C(n-1,1)*num[1]+...+C(n-1,i)*num[i]+...+C(n-1,n-1)*num[n-1].
现在给出n和top,我们可以算出C(n-1,i), 0≤i<n。要计算金字塔的个数,即转化成了上式中top可由C(n-1,i)和num[i], 0≤i<n表达的种类数。显然这个问题转化成了完全背包问题,但有两个问题需要解决:
(1) 题目中给出n的范围是[1,1000000],显然n在很大的时候计算组合数C(n-1,i)是不可能的。但我们注意到num[i]≥1, 基于上式可得
top≥C(n-1,0)+C(n-1,1)+...+C(n-1,n-1)=2^(n-1).
题目中给出top的范围是[1,1000000], 所以
2^(n-1)≤1000000,
由于n是正整数,我们可得n的最大值是20,C(n-1,i)是很好计算的!另外,我们发现当n>20的时候,top无法被表示,可以直接输出0.
(2) num[i]≥1, 0≤i<n, 这一点跟完全背包有一点差别。因为完全背包中每件物品可以取0次,但是这里至少为1. 这个问题很好解决,令top=top-ΣC(n-1,i), 即先把每个C(n-1,i)放进背包里,然后num[i]就变成了≥0,这样就转化成了完全背包问题。
定义dp[i][j]为数j可由前i个数表达的种类数,那么状态转移方程为
dp[i][j]=dp[i-1][j]+dp[i][j-C(n-1,i)],
也即dp[j]=dp[j]+dp[j-C(n-1,i)], 初始化条件dp[0]=1. 完整代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> using namespace std; int num[22]; int dp[1000002]; int main() { int baseLength, top; int i, j; int n; long long a, b; int sum; int con = 1000000009; while (scanf("%d%d", &baseLength, &top) == 2) { if (baseLength > 20) { printf("0\n"); continue; } if (baseLength == 1) { printf("1\n"); continue; } n = baseLength - 1; num[0] = 1; a = n; b = 1; sum = num[0]; for (i = 1; i <= n / 2; i++) { num[i] = a / b; sum += num[i]; a *= (n - i); b *= (i + 1); } for (i = n / 2 + 1; i <= n; i++) { num[i] = num[n - i]; sum += num[i]; } top -= sum; if (top < 0) printf("0\n"); else { memset(dp, 0, sizeof(dp)); dp[0] = 1; for (i = 0; i < baseLength; i++) for (j = num[i]; j <= top; j++) dp[j] = (dp[j] + dp[j - num[i]]) % con; printf("%d\n", dp[top]); } } return 0; }