完全背包(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. 完整代码如下:

#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;
}
View Code

posted on 2019-03-07 14:10  小叶子曰  阅读(170)  评论(0编辑  收藏  举报

导航