AcWing 6. 多重背包问题 III 单调队列优化多重背包

版权声明:https://blog.csdn.net/huashuimu2003 https://blog.csdn.net/huashuimu2003/article/details/89818717

title

AcWing 6

N N 种物品和一个容量是 V V 的背包。
i i 种物品最多有 s i si 件,每件体积是 v i vi ,价值是 w i wi
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式

第一行两个整数, N V ( 0 < N 1000 , 0 < V 20000 ) N,V (0<N≤1000, 0<V≤20000) ,用空格隔开,分别表示物品种数和背包容积。
接下来有 N N 行,每行三个整数 v i , w i , s i vi,wi,si ,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式

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

数据范围

0 < N 1000 0<N≤1000
0 < V 20000 0<V≤20000
0 < v i , w i , s i 20000 0<vi,wi,si≤20000

提示

本题考查多重背包的单调队列优化方法。

输入样例

扫描二维码关注公众号,回复: 6124342 查看本文章

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

10

analysis

\quad 在之前的二进制解法中, D P DP 数组省略了“阶段”这一维。当外层循环进行到 i i 时, F [ j ] F[j] 表示从前 i i 种物品中选出若干个放入背包,体积之和为 j j 时,价值之和最大是多少。 倒序循环 j j ,在状态转移时,考虑选取第 i i 个物品的个数 c n t cnt
F [ j ] = max 1 c n t C i { F [ j c n t V i ] + c n t W i } F[j]=\max_{1 \le cnt\le C_i}\left\{F[j-cnt*V_i]+cnt*W_i\right\}

  • 画出能够转移到状态 j j 决策候选集合 { j c n t V i 1 c n t C i } \left\{j-cnt*V_i|1\le cnt\le C_i\right\}
    在这里插入图片描述
  • 当循环变量 j j 减小 1 1 时:
    在这里插入图片描述
  • 可以发现,相邻两个状态 j j j 1 j-1 对应的决策候选集合没有重叠,很难快速地从 j 1 j-1 对应的集合得到 j j 对应的集合。

\quad 但是,我们适合考虑一下状态 j j j V i j-V_i
C i = 3 C_i=3 在这里插入图片描述
\quad 这两者对应的决策候选集合之间的关系,与我们前面讲解的单调队列题目非常相似,只有一个新决策加入候选集合、一个已有决策被排除。所以,我们应该把状态 j j 按照除以 V i V_i 的余数分组,对每一组分别进行计算,不同组间的状态在阶段 i i 不会互相转移:
\quad 余数为 0 0 —— 0 V i 2 V i 0,V_i,2V_i,···
\quad 余数为 1 1 —— 1 1 + V i 1 + 2 V i 1,1+V_i,1+2V_i,···
\quad ······
\quad 余数为 V i 1 V_i-1 —— V i 1 V i 1 + V i V i 1 + 2 V i (V_i-1),(V_i-1)+V_i,(V_i-1)+2V_i,···
\quad “倒序循环 j j 的过程,改为对每个余数 x [ 0 , V i 1 ] x\in[0,V_i-1] ,倒序循环 p = ( M x ) / V i 0 p=\lfloor \left( M-x\right) /V_{i}\rfloor \sim 0 ,对应的状态就是 j = x + p V i j=x+p*V_i i i 种物品只有 C i C_i 个,故能转移到 j = x + p V i j=x+p*V_i 的决策候选集合就是 { x + k V i p C i k p 1 } \left\{x+k*V_i|p-C_i \le k \le p-1\right\} 。写出新的状态转移方程:
F [ x + p V i ] = max p C i k p 1 { F [ x + k V i ] + ( p k ) W i } F[x+p*V_i]=\max_{p-C_i \le k \le p-1}\left\{F[x+k*V_i]+(p-k)*W_i \right\}
\quad 把外层循环 i i x x 看做定值,当内层循环变量 p p 减小 1 1 时,决策 k k 的取值范围 [ p C i p 1 ] [p-C_i,p-1] 的上下界均单调减小。状态转移方程等号右侧的石子仍然分为两部分,仅包含变量 p p p W i p*W_i 和仅包含变量 k k F [ x + k V i ] k W i F[x+k*V_i]-k*W_i
\quad 综上所述,我们可以建立一个决策点 k k 单调递减、数值 F [ x + k V i ] k W i F[x+k*V_i]-k*W_i 单调递减的队列,用于维护候选集合。对于每个 p p ,执行单调队列的是三个惯例操作:
\quad 1.检查队头合法性,把大于 p 1 p-1 的决策点出队。
\quad 2.取队头为最优决策,更新 F [ x + k V i ] F[x+k*V_i]
\quad 3.把新决策 k = p C i 1 k=p-C-i-1 插入队尾,入队前检查队尾单调性,排除无用决策。
\quad 整个算法的时间复杂度为 O ( N M ) O(NM)

code

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+10,maxv=2e4+10;

template<typename T>inline void read(T &x)
{
    x=0;
    T f=1, ch=getchar();
    while (!isdigit(ch) && ch^'-') ch=getchar();
    if (ch=='-') f=-1, ch=getchar();
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}

int f[maxv],V[maxn],W[maxn];
inline int calc(int i,int x,int k)
{
    return f[x+k*V[i]]-k*W[i];
}

int C[maxn],q[maxv];
int main()
{
    int n,m;read(n);read(m);
    memset(f,0xcf,sizeof(f));
    f[0]=0;

    for (int i=1; i<=n; ++i)
    {
        read(V[i]),read(W[i]),read(C[i]);
        for (int x=0; x<V[i]; ++x)
        {
            int l=1,r=0;
            int maxp=(m-x)/V[i];
            for (int k=maxp-1; k>=max(maxp-C[i],0); --k)
            {
                while (l<=r && calc(i,x,q[r])<=calc(i,x,k)) --r;
                q[++r]=k;
            }

            for (int p=maxp; p>=0; --p)
            {
                while (l<=r && q[l]>p-1) ++l;
                if (l<=r) f[x+p*V[i]]=max(f[x+p*V[i]],calc(i,x,q[l])+p*W[i]);
                if (p-C[i]-1>=0)
                {
                    while (l<=r && calc(i,x,q[r])<=calc(i,x,p-C[i]-1)) --r;
                    q[++r]=p-C[i]-1;
                }
            }
        }
    }

    int ans=0;
    for (int i=1; i<=m; ++i) ans=max(ans,f[i]);
    printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/huashuimu2003/article/details/89818717