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

版权声明:转载注明即可。。 https://blog.csdn.net/huashuimu2003/article/details/89818717

title

AcWing 6

NN 种物品和一个容量是 VV 的背包。
ii 种物品最多有 sisi 件,每件体积是 vivi,价值是 wiwi
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式

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

输出格式

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

数据范围

0<N10000<N≤1000
0<V200000<V≤20000
0<vi,wi,si200000<vi,wi,si≤20000

提示

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

输入样例

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

输出样例:

10

analysis

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

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

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

没有更多推荐了,返回首页