Luogu P2340 [USACO03FALL]Cow Exhibition G
题意简述
给 \(n\) 个物品,每个物品有两个属性(为整数),要求从中选若干个物品,使得两属性的总和最大且任意一个属性的和不得小于 \(0\)
解
题意类似于背包,复杂度 \(W = 400 \times 1000\) 也符合 \(O(nW)\),就用背包的思想来考虑
惯用伎俩,选一个属性为重量,另一个属性为价值,
但是因为由负数重量的存在,所以在使用C++的时候就要将数组大小开到 \(800000\) ,下标 \(400000\) 代表重量为 \(0\) , \(400000\) 以下是负数,以上是正数
这样两个属性的和以及各自的和就能清晰算出了
接下来就是做背包的问题了
对于重量为正数的时候,与普通的01背包相同即可,由大到小更新
对于重量为负数的时候,因为减去一个负数相当于加上一个正数,所以在做背包的时候要从最小向大更新
核心代码:
for(int i=1;i<=n;++i) {
if(w[i]>=0) { // 重量为正数
for(int j=800000;j>=w[i];--j) // 从大往小枚举
f[j] = max(f[j],f[j-w[i]]+v[i]);
} else {
for(int j=0;j<=800000+w[i];++j) { // 重量为负数
f[j] = max(f[j],f[j-w[i]]+v[i]); // 从小往大枚举
}
}
}
最终答案就是
\[\max_{400000\leq i \leq 800000} f_i+i-400000\ \ (f_i\geq 0)
\]
最终代码:
#include<bits/stdc++.h>
using namespace std;
char ch;int fl;
template<typename T>
inline T redn(T &ret) { // 快读
ret=0,fl=1,ch=getchar();
while(ch<'0' || ch>'9') {if(ch=='-')fl=-1;ch = getchar();}
while(ch>='0'&&ch<='9') {ret=ret*10+ch-'0';ch=getchar();}
return ret=ret*fl;
}
int n;
int w[404],v[404],f[400000<<1+1];
int main() {
redn(n);
for(int i=1;i<=n;++i) redn(w[i]),redn(v[i]);
memset(f,128,sizeof f);
f[400000] = 0;
for(int i=1;i<=n;++i) {
if(w[i]>=0) { // 重量为正数
for(int j=800000;j>=w[i];--j) // 从大往小枚举
f[j] = max(f[j],f[j-w[i]]+v[i]);
} else {
for(int j=0;j<=800000+w[i];++j) { // 重量为负数
f[j] = max(f[j],f[j-w[i]]+v[i]); // 从小往大枚举
}
}
}
int ans = 0;
for(int i=400000;i<=800000;++i) if(f[i]>=0) ans = max(ans,f[i]+i-400000);
printf("%d",ans);
}