多重背包

【题目描述】

现有一个容量为M的背包和N种物品,每种物品都有三个属性Vi、Wi、Ci,分别表示这种物品的体积、价值以及件数。

要求从所给的物品中选出若干件,其体积之和不能超过背包容量,且使所选物品的价值之和最大。

【输入描述】

第一行输入两个整数N、M;

接下来N行,每行输入三个整数Vi、Wi、Ci

【输出描述】

输出一个整数,表示最大的价值之和。

【样例输入】

2 8

2 100 4

4 100 2

【样例输出】

400

【数据范围及提示】

对于20%的数据,Ci=1;

对于60%的数据,N,M <= 500,Ci <= 100;

对于100%的数据,N,M <= 3000,Ci <= 3000。

源代码:

#include<cstdio>
#include<cstring>
int N,M,f[6001]; //最好开两倍,不明觉厉的数据范围。
void Solve(int V,int W,int C)
{
    if (V>M) //特判直接退出。
      return;
    int Head,Tail,Q[6001],F[6001];
    for (int a=0;a<V;a++) //实在装不下该物品的冗余空间。
    {
        Head=1;
        Tail=0;
        Q[++Tail]=a;
        F[a]=f[a]; //初始化。
        for (int b=V+a;b<=M;b+=V) //枚举装下几个该物品的空间。
        {
            while (Head<=Tail&&f[Q[Tail]]-Q[Tail]/V*W<=f[b]-b/V*W) //若此时的冗余空间价值大于队尾的。
              Tail--;
            Q[++Tail]=b; //Q[]存储的是空间。
            if ((b-Q[Head])/V>C) //若数量超过则去头,因为顺序循环,所以不必使用while()。
              Head++;
            F[b]=f[Q[Head]]-Q[Head]/V*W+b/V*W; //取最大冗余价值,然后加上应得的物品价值。
        }
        memcpy(f,F,sizeof(F)); //小白鼠数组。
    }
}
int main() //多重背包+单调队列优化。
{
    scanf("%d%d",&N,&M);
    for (int a=1;a<=N;a++)
    {
        int V,W,C;
        scanf("%d%d%d",&V,&W,&C);
        Solve(V,W,C);
    }
    printf("%d",f[M]);
    return 0;
}

/*
    想一想,为什么这样处理是正确的呢?
    首先会发现,在处理过程中,所有空间情况都只会处理一遍。
    其次,冗余空间的价值不仅包括非物品价值,还包括了比物品更多的价值。
    其实上面都是废话。
    多重背包问题:f[i][j]=max(f[i-1][j-k*V[i]]+k*W[i]) (0 <= k <= C[i]);
    把它变一变,设A=j/V[i]、B=j%V[i],并用k替换(A-k),于是状态转移方程变为:
        f[i][j]=max(f[i-1][B+k*V[i]]-k*W[i])+A*W[i] (A-C[i] <= k <= A)。
*/
原文地址:https://www.cnblogs.com/Ackermann/p/5966275.html