P8392 [BalticOI 2022 Day1] Uplifting Excursion
P8392 [BalticOI 2022 Day1] Uplifting Excursion
题意
你有 \(2m+1\) 种物品,重量分别是 \([-m,m]\),每种物品有 \(a_i\) 个。问最多选择多少个物品使得总重量恰好是 \(l\)。
\(m \le 300,|l| \le 10^{18}, a_i \le 10^{12}\)。
思路
减少 DP 状态复杂度。
朴素多重背包,复杂度是 \(O(ml)\) 的。
注意到每个物品的价值都是 \(1\),且物品个数很少,且物品重量是区间 \([-m,m]\),考虑围绕这些特点思考。
众所周知选到恰好 \(l\) 的价值是不好做的,但是选到一个长度为 \(V=m\) 的价值区间是简单的,你随便选都能选到这样一个区间之内。
考虑到我们的物品种类和重量都很小,我们可以先选择尽可能多的物品使得总重量在区间 \([l-m,l]\) 之内。做法是先全部物品选上,如果总重量太大,就优先扔掉重量大的物品,否则就优先扔掉重量小的物品。
然后再做:退掉一些物品或者添加一些物品,使得总重量恰好是 \(s\),其中 \(s\le m\)。
退掉一些物品,可以看做加入取反重量和价值的物品。因此问题是有 \(4m+2\) 种物品,重量在 \([-m,m]\) 范围内,价值 \(\in \{1,-1\}\),问填满 \(s \le m\) 大小的背包,最大价值。
背包要计算时要开多大呢?
我们目前选择的物品满足总质量在 \([l-m,l]\) 时价值最大。所以不会出现任何 DP 值比 \(0\) 大。我们要做的背包仅仅是找出最优的刚好凑到总质量是 \(l\) 的方法。
根据一点余数的分析,一个物品最多取 \(m\) 个,背包开 \(m^2\)。
准确一点算,开 \(2 \cdot \frac{(m+1)(m)}{2}\),开大一点吧,开 \(2m^2\)。加上负的情况,一共开 \(4m^2\) 好了。
那么做多重背包,使用单调队列优化的话,总时间复杂度 \(O(m^3)\)。
懒得写单调队列优化,写二进制拆分,复杂度 \(O(m^3\log a)\)。常数小,也能过。
2025.6.13
最近又做到这个题,然后感觉很眼熟啊,翻到这篇题解。拼尽全力翻出了写了一半的代码。
怎么能咕这么久的。
代码写一半就藏在文件夹深处不是一个好习惯。
算了还是写单调队列优化多重背包吧,学习一下也好。
code
不算难写吧。细节稍微有一点多。
#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace wing_heart {
constexpr int N=303,inf=1e9+7;
int n;
ll l;
ll m[N<<2];
int w[N<<2],v[N<<2];
int f[N*N<<2];
int id(int x) { return x+n; };
int iid(int x) { return x-n; };
int reid(int x) { return x+((n<<1)|1); }
struct pii {
int x,y;
};
vector<pii> que[N];
void main() {
sf("%d%lld",&n,&l);
rep(i,0,n<<1) sf("%lld",&m[i]), w[i]=iid(i), v[i]=1, w[reid(i)]=-w[i],v[reid(i)]=-1;
ll sumw=0,sumv=0;
rep(i,0,n<<1) sumw+=m[i]*w[i], sumv+=m[i];
//[l-n+1,l]
if(sumw>l) {
for(int i=n;i>=1&&sumw>l;i--) {
int p=id(i);
ll k=min(m[p],(ll)ceil(1.0*(sumw-l)/w[p]));
m[reid(p)]+=k, m[p]-=k;
sumw-=k*w[p], sumv-=k;
}
}else {
for(int i=-n;i<=-1&&sumw<l-n+1;i++) {
int p=id(i);
ll k=min(m[p],(sumw-l)/w[p]);
m[reid(p)]+=k, m[p]-=k;
sumw-=k*w[p], sumv-=k;
}
}
if(sumw<l-n+1 || sumw>l) {
puts("impossible");
exit(0);
}
rep(i,0,n<<1) swap(m[i],m[reid(i)]);
//多重背包
int s=l-sumw;
int t=2*n*n;
rep(i,0,t<<1) f[i]=-inf;
f[t]=0;
rep(i,0,reid(n<<1)) {
if(w[i]==0 || m[i]==0) continue;
if(w[i]>0) {
rep(j,0,w[i]-1) while(que[j].size()) que[j].clear();
rep(j,0,t<<1) {
int p=(j-t)%w[i];
p=p<0?p+w[i]:p;
int c=j/w[i];
while(que[p].size() && que[p][0].y<(c-m[i])) que[p].pop_back();
if(f[j]!=-inf) {
int val=f[j]-c*v[i];
while(que[p].size() && que[p].back().x<=val) que[p].pop_back();
que[p].push_back({val,c});
}
if(que[p].size()) {
f[j]=max(f[j],que[p].front().x+(c*v[i]));
}
}
} else {
rep(j,0,-w[i]-1) while(que[j].size()) que[j].clear();
per(j,t<<1,0) {
int p=(j-t)%w[i];
p=p<0?p-w[i]:p;
int c=j/w[i];
while(que[p].size() && que[p][0].y<(c-m[i])) que[p].pop_back();
if(f[j]!=-inf) {
int val=f[j]-c*v[i];
while(que[p].size() && que[p].back().x<=val) que[p].pop_back();
que[p].push_back({val,c});
}
if(que[p].size()) {
f[j]=max(f[j],que[p].front().x+(c*v[i]));
}
}
}
}
if(f[s+t]==-inf) {
puts("impossible");
exit(0);
}
pf("%lld\n",sumv+f[s+t]);
}
}
int main() {
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("my.out","w",stdout);
#endif
wing_heart :: main();
}
本文来自博客园,作者:wing_heart,转载请注明原文链接:https://www.cnblogs.com/wingheart/p/18622528

浙公网安备 33010602011771号