题外话
2020
/
11
/
29
,
在
Z
J
N
U
个
人
赛
遇
到
了
一
样
的
题
但
是
过
不
了
。
\color{red}2020/11/29,在ZJNU个人赛遇到了一样的题但是过不了。
2020/11/29,在ZJNU个人赛遇到了一样的题但是过不了。
于
是
发
现
了
这
题
的
蹊
跷
。
\color{red}于是发现了这题的蹊跷。
于是发现了这题的蹊跷。
这题所有能过的贪心策略的题解可能都有问题!
难度
普 及 + / 提 高 \color{green}普及+/提高 普及+/提高
题意
物品
N
N
N 种,背包容积
V
V
V
每样物品有体积
v
i
v_i
vi , 重要度
p
i
p_i
pi。
求01背包之后的重要度和最大为多少。
特殊条件:
min
i
(
v
i
)
≤
v
i
≤
min
i
(
v
i
)
+
3
\underset{i}{\min}(v_i)\le v_i\le \underset{i}{\min}(v_i)+3
imin(vi)≤vi≤imin(vi)+3
并且他装不下所有的物品。
数据范围
1
≤
N
≤
100
1\le N\le 100
1≤N≤100
1
≤
W
≤
1
0
9
1\le W\le 10^9
1≤W≤109
1
≤
v
i
≤
1
0
9
1\le v_i\le 10^9
1≤vi≤109
1
≤
p
i
≤
1
0
7
1\le p_i\le 10^7
1≤pi≤107
思路
- 物品种类虽然少,但是背包体积太大了,普通的 O ( N V ) O(NV) O(NV) 背包肯定没法做。
- 考虑特殊条件,我们有两种做法:
- 【做法1】
- 因为最多就物品体积有四种,每种体积我们肯定优先选择重要度最大的。
- 因此我们枚举每种体积我们选几种物品。
- 时间复杂度 O ( N 4 ) O(N^4) O(N4),经过优化可以 O ( N 3 ) O(N^3) O(N3)。
- 但是如果物品体积有 x x x 种,时间复杂度 O ( N x ) O(N^x) O(Nx) 怎么优化都不是很优秀的样子。
- 【做法2】
- 因为物品种类很少,且物品体积差别也很小。我们考虑什么时候无论怎么选都只能选固定数量的物品
- 易得,当 ⌊ V min v i ⌋ = ⌊ V max v i ⌋ \lfloor \frac{V}{\min v_i} \rfloor=\lfloor \frac{V}{\max v_i}\rfloor ⌊minviV⌋=⌊maxviV⌋时成立。并且有隐含条件:
- max v i = min v i + 3 \max v_i=\min v_i+3 maxvi=minvi+3
- V < N × max v i V<N\times\max v_i V<N×maxvi
- 化简可得:
- 综上:
- 当 min v i < 300 \min v_i<300 minvi<300时,普通的01背包即可,时间复杂度 O ( N V ) = O ( N × N × max v i ) = O ( N 3 ) O(NV)=O(N\times N\times \max v_i)=O(N^3) O(NV)=O(N×N×maxvi)=O(N3)
- 当 min v i ≥ 300 \min v_i \ge 300 minvi≥300时,因为只能选择固定的 V min b i \frac{V}{\min b_i} minbiV个物品,每次贪心选择重要度最高的即可,时间复杂度 O ( N log N ) O(N\log N) O(NlogN)
但是 上 述 策 略 并 不 正 确 ! \color{red}上述策略并不正确! 上述策略并不正确!
- 因为对于某一个 min = V k \min=\frac{V}{k} min=kV,易得 min = max + 1 \min =\max +1 min=max+1, min ≠ max \pmb{\min\ne\max} min=maxmin=maxmin=max
- 因此, 贪 心 策 略 得 逞 于 题 目 数 据 氺 了 \color{red}贪心策略得逞于题目数据氺了 贪心策略得逞于题目数据氺了
正解DP
- 因为每个物品的体积很大,我们缩小体积为 v i = v i − min v v_i=v_i-\min v vi=vi−minv
- 设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 选了i个物品,新的背包体积为j时的最大收益
- 易得,如果我们选择了 i i i 个物品,那么目前的 原来背包的体积为 i × min v + k i\times \min v+k i×minv+k
- 合法转移,我们需要现在的背包体积合法、原来的背包体积也合法。
- 那么,我们 修改后的背包体积就类似于新的背包的属性了,就变成了二维背包
- 二维01背包的每一个属性都需要 逆序枚举即可。
核心代码
时间复杂度为 O ( x N 3 ) O(x N^3) O(xN3)
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
const int MAX = 3e3+50;
const int INF = 0x3f3f3f3f;
int w[MAX];
int v[MAX];
int dp[MAX][MAX];
int main()
{
int N;
ll M;
cin >> N >> M;
int mn = INF;
for(int i = 1;i <= N;++i){
cin >> w[i] >> v[i];
mn = min(mn,w[i]);
}
for(int i = 1;i <= N;++i)
w[i] -= mn;
int ans = 0;
for(int i = 1;i <= N;++i)
for(int j = min(N,(int)M / mn);j >= 1;--j)
for(int k = 3 * j;k >= w[i];--k){
if((ll)j * mn + k <= M)dp[j][k] = max(dp[j][k],dp[j-1][k-w[i]]+v[i]);
ans = max(ans,dp[j][k]);
}
cout << ans;
return 0;
}