背包
![](https://img2018.cnblogs.com/i-beta/1755645/201911/1755645-20191108080328100-1320046865.png)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cmath> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <iostream> 5 #include <algorithm> 6 const int MAXN = 100; 7 const int MAXV = 200000; 8 using namespace std; 9 10 int n, V; 11 int num[MAXN + 10], volume[MAXN + 10], value[MAXN + 10];//略 12 13 long long f_arr[MAXV + 10];//数组而已,只不过用指针来表示 这个是完全背包滚动数组 14 long long g_arr[MAXV + 10];// 15 long long *f = f_arr;// 16 long long *g = g_arr;// 17 18 int fr_arr[MAXV + 10], nfr_arr[MAXV + 10];//意义不明猜测记录路径 19 int *fr = fr_arr; 20 int *nfr = nfr_arr; 21 22 int q[MAXV + 10];//记录当前物品选择个数的数组 23 24 int vis[MAXV + 10];//记录是否访问过 25 int Y;//懒标 26 27 long long ans = 0;//最大价值 28 int ans_r[MAXN + 10];//路径 不明如何计算 29 30 void dp(int l, int r, int s, int t) { 31 if (r - l == 1)//当只有一种可以枚举 ,不能再找中点 32 return; 33 34 int mid = (l + r + 1) >> 1; // r - l = 1, choose r, correctness trick (not used in this prob)洋屁看不懂,大概意思是在r-l=1的情况下选择r,是一个技巧(但在这段代码里没有使用) 35 36 f[s] = 0, fr[s] = -1, vis[s] = ++Y;//vis懒标 <-- 37 for (int i = l+1; i <= r; ++i) {//枚举第一个物品 38 for (int j = 0; j < volume[i]; ++j) {//当前容积 39 if (s + j > t) //超出当前背包容积 40 break; 41 int maxk = (t - (s + j)) / volume[i]; 42 int ht = 1, qt = 0;//头和尾存当前这个物品放的个数 43 int pos = s + j;//放了当前物品的体积 44 for (int k = 0; k <= maxk; ++k) {//完全背包模板 45 while (ht <= qt && k - q[ht] > num[i]) 46 ++ht; 47 48 if (vis[pos] == Y) {//如果作为起点访问过 当前q栈里存的方案不够好弹出 49 while (ht <= qt && f[q[qt] * volume[i] + j + s] + (long long) value[i] * (k - q[qt]) <= f[pos]) 50 --qt; 51 52 q[++qt] = k;//选k个,把较差的情况从q栈中弹出 53 } 54 55 if (ht <= qt) {//f和g互换 滚动数组 56 g[pos] = f[q[ht] * volume[i] + j + s] + (long long) value[i] * (k - q[ht]);//当前q栈中存的一定是最优的情况 57 58 if (i == mid) nfr[pos] = pos;//路径中点,即(n/2,x) 所以这两个fr nfr是拿来存中点的?? <-- 59 else nfr[pos] = fr[q[ht] * volume[i] + j + s]; 60 61 vis[pos] = Y;//懒标,表示现在这个位置来过 62 } 63 else 64 g[pos] = 0, nfr[pos] = -1;//当前情况就是q栈中没有数据,即没有更好的方案,但清零并不能理解 65 66 pos += volume[i];//当前背包体积加上当前的物品体积进行下一轮计算 67 } 68 } 69 swap(f, g);//数组滚动 70 swap(fr, nfr); 71 } 72 73 ans_r[mid] = fr[t];//<--记录中点 74 75 if (l == 0 && r == n) // first dp, save the ans ans记录完全背包的最大值 76 ans = f[t]; 77 78 dp(l, mid, s, ans_r[mid]);//开分 79 dp(mid, r, ans_r[mid], t); 80 } 81 82 int main() { 83 freopen("knapsack.in", "r", stdin); 84 freopen("knapsack.out", "w", stdout); 85 cin >> n >> V; 86 for (int i = 1; i <= n; ++i) 87 cin >> num[i] >> volume[i] >> value[i];//数量 体积 价值 88 89 reverse(num + 1, num + n + 1);//反转,其实可以从后往前读入 90 reverse(volume + 1, volume + n + 1); 91 reverse(value + 1, value + n + 1); 92 93 ans_r[0] = 0, ans_r[n] = V;//初始化?? 94 dp(0, n, 0, V);//dp入口 95 96 cout << ans << endl;//最大价值 97 for (int i = n; i >= 1; --i) { 98 cout << (ans_r[i] - ans_r[i-1]) / volume[i];//输出路径开始意义不明 99 100 ans -= (ans_r[i] - ans_r[i-1]) / volume[i] * value[i];//ans到现在没什么软用了吧,可能检验用的 101 102 if (i != 1) 103 cout << " ";//输出格式 104 else 105 cout << endl; 106 } 107 }