[BZOJ2287][POJ Challenge 1009] 消失之物-题解

Description

ftiasch 有 N 个物品, 体积分别是 W1W2, …, WN。 由于她的疏忽, 第 i 个物品丢失了。 “要使用剩下的 N – 1 物品装满容积为 x 的背包,有几种方法呢?” — 这是经典的问题了。她把答案记为 Count(i, x) ,想要得到所有1 <= i <= N, 1 <= x <= M的 Count(i, x) 表格。

Input

第1行:两个整数 N (1 ≤ N ≤ 2 × 103) 和 M (1 ≤ M ≤ 2 × 103),物品的数量和最大的容积。

第2行: N 个整数 W1W2, …, WN, 物品的体积。

Output

一个 N × M 的矩阵, Count(i, x)的末位数字。

Sample Input

3 2
1 1 2

Sample Output

11
11
21

HINT

如果物品3丢失的话,只有一种方法装满容量是2的背包,即选择物品1和物品2。

首先,一个很简单的问题是求考虑所有前n个物品凑出每一种体积的方案数,这是一个硬币找零的模型,用f[i][j]表示使用前i个物品凑出体积j的方案数,f[i][j]=f[i-1][j]+(j-a[i]>=0)?f[i-1][j-a[i]]:0。难就难在他要每次去掉一个,然后分别求出去掉后的f。如果是朴素做法的话,每一次都是O(n*m),一共n次,爆炸得很厉害。可以发现,这个问题有点像做加法算式,1+2+...+100,问你去掉1,2,3...后再分别求和,得到100个和。那么这个题的话肯定不会去针对每一个去掉的数都把剩余的99个数加起来(要算99*100次),而是先把1+2+...100先算出来,再分别减去1,2,3...这样的话运算量大大减小(只算100+100次)。所以,这道题也可以类比过来,用相似的做法来解决。可以发现,朴素做法每一次计算f都有大量相同的物体(只有一个不一样),所以浪费了很多的计算结果。那么,我先把f[n][j]算出来,表示考虑所有物品凑出j的方案数,然后由于物品的排列顺序是无关紧要的,也就是说,对于f[n][j]来说,前n个物品只要是那个集合就行,跟他们排列的顺序无关,1,2,3...n还是1,3,2...n其实不影响结果,因为这些元素就是那么些东西,怎么凑跟他怎么排无关。于是就可以把要去掉的元素i放最后,变成1,2,...i,那么假设此时前n-1个元素的f为f[n-1][j](注意不是原来的f[n-1][j],因为我把i放最后,现在前n-1个元素改变了,不是原来的了),那么按照递推式,f[n][j]=f[n-1][j]+(j-a[i]>=0)?f[n-1][j-a[i]]:0,如果j<a[i],那就好办f[n-1][j]=f[n][j],如果j>=a[i],那么按照递推式,f[n][j]=f[n-1][j]+f[n-1][j-a[i]],所以f[n-1][j]=f[n][j]-f[n-1][j-a[i]],f[n][j]是已知的,那么我只需要按照j从小到大来枚举,那么由于j-a[i]<j,所以遇到第二种情况时也能保证f[n-1][j-a[i]]已经被算出来了,这样的话,只需要枚举每一个i,对应的算出f[n-1][j],就解决了问题。

这个问题启发我考虑动态规划问题不仅可以是在一个子问题上增加一个物品得到一个更大的子问题,还可以是在一个子问题上减掉一个物品得到一个更小的子问题。但是,我尝试把它推广到0/1背包问题,却发现不行,就是这种要取max/min的好像挺不好搞,因为类比过去f[n-1][j]可能在f[n][...]里面找不到能算出他的,因为取max的过程会丢失信息。

最后,给出一个可行的答案,注意要随时取余,防止加着加着就炸了。另外,实现的时候把f化为一维的了,这是背包问题的做法,g就是对一个特定的去掉的元素的f[n-1]

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int n,m,a[2005];
int f[2005],g[2005];
int main() {
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) {
        scanf("%d",&a[i]);
    }
    f[0]=1;
    for (int i=1;i<=n;i++) {
        for (int j=m;j>=1;j--) {
            if (j-a[i]>=0) f[j]=(f[j]+f[j-a[i]])%10;
        }
        /*for (int j=1;j<=m;j++) {
            printf("f[%d][%d]=%d\n",i,j,f[j]);
        }*/
    }
    g[0]=1;
    for (int i=1;i<=n;i++) {
        for (int j=1;j<=m;j++) {
            if (j-a[i]<0) {
                g[j]=f[j];
                printf("%d",g[j]);
            }
            else {
                g[j]=(f[j]-g[j-a[i]]+10)%10;
                printf("%d",g[j]);
            }
        }
        printf("\n");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/dengping_ss/article/details/82956026
今日推荐