title: C.How_to_Fail_at_Programming_Contest
date: 2019-10-05 22:20:11
categories:
- ACM
tags: - 揹包
- 基礎dp
最近比賽碰到了一道揹包問題,大意就是在揹包儘可能裝的多的情況下
獲取最小价值。給?整蒙⚪了。我就決定複習一下揹包問題。
首先,回顧一下01揹包和完全揹包的轉移方程
01揹包
狀態轉移方程: F[i][j] = max{ F[i-1][j], F[i-1][j- C[i]] + W[i] }
//倒着滾
void ZeroOnePack(F,C,W){
for(v = V to C){
F[v] = max(F[v],F[v-C] + W)
}
}
就是拿和不拿
完全揹包
狀態轉移方程: F[i][j] = max{ F[i-1][j], F[i][j-C[i]] + W[i] }
//順着滾
void CompletePack(F,C,W){
for(v = C to V){
F[v] = max(F[v],F[v-C] + W)
}
}
因爲可以拿多個所以直接在第i層滾動就可以了,不用去i-1層
所以01揹包用滾動數組的時候是 倒着滾,因爲要用前一層的。
而完全揹包用滾動數組的時候要 正着滾 。
多重揹包
就是物品有多個,但是有限。
def MultiplePack(F,C,W,M){ //M件物品
if (C*M >= V){ //足夠多,可以視爲完全揹包
CompletePack(F,C,W)
return
}
k = 1 //二進制思想
while(k < M){
ZeroOnePack(kC,kW)
M = M - k
k = 2k
}
}
害,簡單的複習了一下。
揹包九講還有好多沒有看完,還要再慢慢琢磨琢磨。
其實主要就是補一下昨天的C題。
題意上面說了。
解題思路: 維護 F[i][j] 的同時 再維護一個 left[i][j]
F[i][j] 表示前 i 個物品的時候的最小价值,left[i][j] 表示前 i 個物品的時候取最小价值的時候所剩餘的空間
轉移的時候,當left[i-1][j] >= C[i] 的時候,此時必須選第i道題目,如果不選,和題意違背
這點很重要!!
在轉移的時候分兩種情況
選和不選
選
則
F[i][j] = F[i-1][j - C[i]] + W[i];
left[i][j] = left[i-1][j - C[i]]; //選了後剩餘的空間不變,多出來的空間被選的物品佔滿了
不選
F[i][j] = F[i-1][j];
left[i][j] = F[i-1][j-1];
用滾動數組優化後
核心代碼
for (int v = V; v >= C[i]; v--) {
if (L[v] >= C[i] || F[v - C[i]] + W[i] < F[v]) {
F[v] = F[v - C[i]] + W[i];
L[v] = L[v - C[i]];
}
}
當然,處理前要按C[i]從小到大排序
完整代碼
話說這樣的排序就很懶。
#include<bits/stdc++.h>
using namespace std;
int C[3000], W[3000];
int r[3000];
int n, V;
int L[3000], F[3000];
bool cmp(int x, int y) {
return(C[x] < C[y]);
}
int main() {
cin >> n >> V;
for (int i = 1; i <= n; i++) {
cin >> C[i] >> W[i];
r[i] = i;
}
sort(r + 1, r + 1 + n, cmp);
for (int i = 0; i < 2050; i++) {
F[i] = 0;
L[i] = i;
}
for (int k = 1; k <= n; k++) {
int i = r[k];
for (int v = V; v >= C[i]; v--) {
if (L[v] >= C[i] || F[v - C[i]] + W[i] < F[v]) {
F[v] = F[v - C[i]] + W[i];
L[v] = L[v - C[i]];
}
}
}
cout << F[V] << endl;
}