[日常摸鱼]一些DP题(2)各种背包——01背包/二维背包/背包前K优解

https://codeforces.com/problemset/problem/19/B

Bob拿着\(n\)件商品在收银台付款,扫描第\(i\)件商品需要\(t_i\)的时间,第\(i\)件的价格为\(c_i\),在扫描的时候可以选择偷走一些商品,偷走一个商品需要1个单位的时间,问最少花多少钱能获得所有商品。\(n\leq 2000,t_i\leq 2000\)

相当于把商品划分成两个集合\(S,T\),满足\(|T|\leq \sum_{S}t_i\),使得\(\sum_{S}c_i\)最小,左边的式子稍微变形会得到\(\sum_{T}t_i+|T|=\sum_{T}(t_i+1)\leq \sum t_i\),这就很像背包了:反着考虑获得哪些物品不要花钱,总容量为\(\sum t_i\),选择一件物品不花钱得到的代价是\(t_i+1\),这样一个01背包问题,但是复杂度会达到\(O(nt^2)=O(n^3)\),接受不了。

不过顺着这个背包的思路继续想,选择花钱买一件物品相当于多获得一个\(t_i+1\)的体积,对应的付出\(c_i\)的代价,最终目标是获得所有物品,即\(\sum_{S}t_i+1\geq n\),于是又是一个背包:选择花钱购买一些物品,使得这些物品的体积之和超过\(n\),求最小的代价。同样的问题又来了,这样做背包的体积上界是多少?如果还是\(O(n^2)\)级别的话这个优化就没什么用了:仔细想一下上界不会很大,在一系列决策之后如果当前的\(\sum t_i+\)已经超过了\(n\),那后面的一定不会继续选择购买,所以最大的情况一定是从一个小于\(n\)的体积跨越到一个大于\(n\)的体积,对应的上界就是\(n-1+(v_{max}+1)=n+v_{max}\)了。


https://www.luogu.com.cn/problem/P4141

背包问题变形,\(n\)个物品,需要回答如果没有第\(i\)个物品的时候,恰好装满容量为\(x=1,\dots m\)的背包需要多少代价?\(n,m\leq 2000\)

原问题是\(dp[i][j]=dp[i-1][j]+dp[i-1][j-v[i]]\)\(dp[0][0]=1\),暴力做是直接\(O(n^2m)\)的,先处理处没有第一个物品的答案,然后考虑如何给dp删除一个物品和添加物品。

按照滚动数组得到的dp数组考虑,注意到物品顺序不影响答案,假设当前得到的是\(f[0,\dots,m]\),删去物品\(i\)之后的答案是\(g[1,\dots,m]\),则有当\(j<v[i]\)时,\(f[j]=g[j]\),当\(j\geq v[i]\)\(f[j]=g[j]+g[j-v[i]]\),于是就能从小到大反推出\(g[]\),这样每一次只需要\(O(m)\)的代价计算删除和加入一个物品的答案。

rep(i,2,n){ 
    rep(j,0,w[i]-1)g[j]=f[j];
    rep(j,w[i],m)g[j]=(f[j]-g[j-w[i]]+10)%10;

    rep(j,0,m)f[j]=g[j];
    for(int j=m;j>=w[i-1];j--)upd(f[j],f[j-w[i-1]]);
    rep(j,1,m)printf("%c",f[j]+'0');
    puts("");
}

https://www.luogu.com.cn/problem/P1877

01背包变形,\(dp[i][j]\)\(dp[i-1][j-c[i]]\)\(dp[i-1][j+c[i]]\)两个转移。


https://www.luogu.com.cn/problem/P1509

二维背包变形,两个代价(rmb和rp)以及两个收益(在泡到最多MM的前提下时间最小),可以考虑两个dp数组,在MM最多的前提下再比较第二维,或者像我在实现的时候直接开个struct来存dp,重载一个比较函数。


https://www.luogu.com.cn/problem/P3985

二维背包变形,一开始理解错题意,以为是DP的时候多带一个极差\(\leq3\)的限制,后面发现原来是给的数据保证极差\(\leq 3\),那就好做了,考虑最后选择的物品的集合是\(S\),最后要\(\sum_{i\in S} v_i\leq W\),取\(X=\min_i(v_i)\),式子变成\(X|S|+\sum_{i\in S}(v_i-X)\leq W\),考虑个双重限制的背包:一个是\(v_i\in{0,1,2,3}\),和第二维代价:每选一个物品代价是1,先对这个二维背包进行DP,然后再统计符合条件的答案。


https://www.luogu.com.cn/problem/P1455

并查集维护连通块,然后直接对每一个联通块01背包


https://www.luogu.com.cn/problem/P1858

\(K\)个人,每个人有一个背包,容量都是\(V\)\(N\)件物品,现在要每个人都能恰好装满背包,并且任意两个人选的物品不完全相同,所有人价值之和的最大\(K\leq 50,V\leq 5000,N\leq 200\)

发现相当于在求一个01背包要求完全装满的前\(K\)大值,前\(K\)大的处理一般是把普通的DP式子转化成一个单调队列来维护,转移变成\(O(k)\)地归并状态。

rep(i,1,n)per(j,V,v[i]){
	tmp.clear();
	for(auto itr:f[j])tmp.pb(itr);
	f[j].clear();

    int p=0,q=0;
    while((p<tmp.size()||q<f[j-v[i]].size())&&(p+q<=k)){
        int sp=tmp.size(),sq=f[j-v[i]].size();
        if(q>=sq||(p<sp&&q<sq&&tmp[p]>w[i]+f[j-v[i]][q]))f[j].pb(tmp[p++]);
        else f[j].pb(w[i]+f[j-v[i]][q++]);
    }
}
posted @ 2021-03-25 21:50  yoshinow2001  阅读(74)  评论(0编辑  收藏  举报