https://codeforces.com/problemset/problem/19/B
Bob拿着(n)件商品在收银台付款,扫描第(i)件商品需要(t_i)的时间,第(i)件的价格为(c_i),在扫描的时候可以选择偷走一些商品,偷走一个商品需要1个单位的时间,问最少花多少钱能获得所有商品。(nleq 2000,t_ileq 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+1geq 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,mleq 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]),当(jgeq 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_{iin S} v_ileq W),取(X=min_i(v_i)),式子变成(X|S|+sum_{iin S}(v_i-X)leq W),考虑个双重限制的背包:一个是(v_iin{0,1,2,3}),和第二维代价:每选一个物品代价是1,先对这个二维背包进行DP,然后再统计符合条件的答案。
https://www.luogu.com.cn/problem/P1455
并查集维护连通块,然后直接对每一个联通块01背包
https://www.luogu.com.cn/problem/P1858
(K)个人,每个人有一个背包,容量都是(V),(N)件物品,现在要每个人都能恰好装满背包,并且任意两个人选的物品不完全相同,所有人价值之和的最大(Kleq 50,Vleq 5000,Nleq 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++]);
}
}