牛客网 货物分组 – 题解

是的,又是一道考试时想出正解没有打出来的题目

货物分组 – 牛客网

根据题意,我们很容易想到一个朴素的类区间DP——令dp(i,j)dp(i, j)表示现在将第ii件货物划分到第jj箱中,且作为箱内的最后一件货物,所需最小花费。容易写出转移:dp(i,j)=mink[0,i)p=k+1iapWdp(k,j1)+p=k+1iap+极差{att[k+1,i]}dp(i, j) = \min\limits_{k \in [0, i) \land \sum\limits_{p=k+1}^{i} a_p \leq W } dp(k, j-1) + \sum\limits_{p=k+1}^{i} a_p + \textrm{极差}\{a_t \mid t \in [k + 1, i]\},初值有dp(0,0)=0dp(0, 0) = 0,应求得minj[1,n]dp(n,j)\min\limits_{j \in [1, n]} dp(n, j)

经过观察,我们发现,当我们钦定的分组方案中,第11kk组的货物重量分别为s1ks_{1 \dots k}是,费用总和必然包含:i=1ksi×i\sum\limits_{i=1}^{k}s_i \times i,也就是 (s1++sk)+(s2++sk)+(s3++sk)++sk(s_1 + \dots + s_k) + (s_2 + \dots + s_k) + (s_3 + \dots + s_k) + \dots + s_k。这就说明,一旦我们决定要将区间[l,r][l, r]中的货物分成新的一组,那么总花费必然再次累积i=lnai\sum\limits_{i=l}^{n} a_i。这就表明,我们根本没有必要关心当前区间具体分成第几组,因为总答案中必定会累积该组左端点及以后的货物的重量总和。

这种思想成为“代价提前计算”,或“更换维度计算贡献”。现在我们就有了改良后的状态:设f(i)f(i)将第ii件货物作为某一组的封口货物,对前ii件货物包装的最小花费。写出转移:f(i)=minj[1,i)f(j)+p=j+1nap+极差{att[j+1,i]} f(i) = \min\limits_{j \in [1, i)}{f(j) + \sum\limits_{p=j+1}^{n}a_p + \textrm{极差}\{a_t \mid t \in [j + 1, i]\}}

现在我们得到了一个优秀的O(n2)\mathrm{O}(n^2)算法,可以获得6060分。但我们进一步将转移式拆开来看,发现对于一个确定的jj,有f(j)+p=j+1napf(j) + \sum\limits_{p=j+1}^{n}a_p永不改变,变化的仅仅是ii不同时的极差{att[j+1,i]}\textrm{极差}\{a_t \mid t \in [j + 1, i]\}罢了。所以,我们考虑有没有更聪明的办法,不必每次枚举jj

容易发现,极差随着区间长度的增加,是非严格单调递增的——因为只存在后来居上的最大值和最小值。同时我们观察,当我们钦定一个jj,在完成f(i)f(i)的更新后(采用极差{att(j,i]}\textrm{极差}\{a_t \mid t \in (j, i]\}),则在更新f(i+1)f(i + 1)时,有三种可能:(j,i+1](j, i + 1]区间的最小值被更新;(j,i+1](j, i + 1]区间的最大值被更新;无法更新极值。前两者都会使得极差{att(j,i+1]}\textrm{极差}\{a_t \mid t \in (j, i + 1]\}极差{att(j,i]}\textrm{极差}\{a_t \mid t \in (j, i]\}更大。同时我们发现,假设在区间[1,i][1, i]中首个较ai+1a_{i+1}大的数(后继)的位置为p0p_0,那么对于区间[p0+1,i+1],[p0+2,i+1],,[i+1,i+1][p_0 + 1, i + 1], [p_0 + 2, i + 1], \dots, [i + 1, i + 1](继承于区间[p0+1,i],[p0+2,i],,[i,i][p_0 + 1, i], [p_0 + 2, i], \dots, [i, i]),它们的极差都会被ai+1a_{i+1}更新。同时,我们假设区间[p0p1,i][p_0 \dots p_1, i]原本采取最大值ap1a_{p_1}计算极差,且有ai+1>ap0ap1a_{i + 1} > a_{p_0} \geq a_{p_1},则有区间[p0+1,i+1],[p0+2,i+1],,[p1,i+1][p_0 + 1, i + 1], [p_0 + 2, i + 1], \dots, [p_1, i + 1],极差都将增长Δ=ai+1ap1\Delta = a_{i+1}-a_{p1}。所以考虑维护一个单调栈,分别保存单调递增/减的前缀区间的最值的位置。容易通过其记录的位置,更新对应区间的极差。

由于对于每一个jj,只有区间[j+1,i][j + 1, i]的右端点在移动,则对于一个确定的ii,区间[j+1,i][j + 1, i]f(j)+p=j+1napf(j) + \sum\limits_{p=j+1}^{n}a_p是一一绑定的,所以我们采用线段树进行极差的区间更新、单点修改、前缀最小值查询的各项操作。每更新一个f(i)f(i),就将点ii的初始值更新为f(i)+p=i+1napf(i) + \sum\limits_{p = i + 1}^n a_p

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
  72. 72
  73. 73
  74. 74
  75. 75
  76. 76
  77. 77
  78. 78
  79. 79
  80. 80
  81. 81
  82. 82
  83. 83
  84. 84
  85. 85
  86. 86
  87. 87
  88. 88
  89. 89
  90. 90
  91. 91
  92. 92
  93. 93
  94. 94
  95. 95
  96. 96
  97. 97
  98. 98
  99. 99
  100. 100
  101. 101
  102. 102
  103. 103
  104. 104
  105. 105
  106. 106
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 10; long long INF = 1ll<<62; int n, W, a[N], tmp1, tmp2, minpos, mnv, mxv; long long f[N], pre[N]; struct st { int s[N], r; void push (int x) { s[++r] = x; } int top () { return s[r]; } int size () { return r; } void pop () { s[r--] = 0; } } mx, mn; struct node { int l, r, tag; long long data; } t[N<<2]; void build (int x, int l, int r) { t[x].l = l, t[x].r = r, t[x].data = INF, t[x].tag = 0; if (l == r) return; int mid = (l + r) >> 1; build (x<<1, l, mid); build (x<<1|1, mid + 1, r); } void push_down (int x) { if (!t[x].tag) return; t[x<<1].tag += t[x].tag, t[x<<1].data += t[x].tag; t[x<<1|1].tag += t[x].tag, t[x<<1|1].data += t[x].tag; t[x].tag = 0; } void update_incre (int x, int L, int R, int delta) { if (t[x].l >= L && t[x].r <= R) { t[x].data += delta, t[x].tag += delta; return; } int mid = (t[x].l + t[x].r) >> 1; push_down (x); if (L <= mid) update_incre (x<<1, L, R, delta); if (R > mid) update_incre (x<<1|1, L, R, delta); t[x].data = min (t[x<<1].data, t[x<<1|1].data); } void update_sing (int x, int tar, long long num) { if (t[x].l == t[x].r && t[x].l == tar) { t[x].data = num, t[x].tag = 0; return; } int mid = (t[x].l + t[x].r) >> 1; push_down (x); if (tar <= mid) update_sing (x<<1, tar, num); else update_sing (x<<1|1, tar, num); t[x].data = min (t[x<<1].data, t[x<<1|1].data); } long long query (int x, int L, int R) { if (t[x].l >= L && t[x].r <= R) return t[x].data; int mid = (t[x].l + t[x].r) >> 1; long long res = INF; push_down (x); if (L <= mid) res = min (query (x<<1, L, R), res); if (R > mid) res = min (query (x<<1|1, L, R), res); return res; } int main () { /* C 吴秋实 */ // freopen ("team.in", "r", stdin); // freopen ("team.out", "w", stdout); scanf ("%d%d", &n, &W); for (int i = 1; i <= n; ++i) { scanf ("%d", a + i); pre[i] = pre[i - 1] + a[i]; } f[1] = pre[n]; build (1, 1, n); update_sing (1, 1, f[1] + pre[n] - pre[1]); mx.push (1); mn.push (1); minpos = 1; mxv = mnv = a[1]; for (int i = 2; i <= n; ++i) { tmp1 = i; mxv = max (mxv, a[i]); mnv = min (mnv, a[i]); while (mx.size ()) { update_incre (1, mx.top (), tmp1 - 1, a[i] - a[tmp1]); if (mx.size () > 1 && a[i] >= a[mx.top ()]) { tmp1 = mx.top (); mx.pop (); } else break; } mx.push (i); tmp2 = i; while (mn.size ()) { update_incre (1, mn.top (), tmp2 - 1, a[tmp2] - a[i]); if (mn.size () > 1 && a[i] <= a[mn.top ()]) { tmp2 = mn.top (); mn.pop (); } else break; } mn.push (i); while (pre[i] - pre[minpos] > W) ++minpos; f[i] = query (1, minpos, i - 1); if (pre[i] <= W) f[i] = min (f[i], pre[n] + mxv - mnv); update_sing (1, i, f[i] + pre[n] - pre[i]); } printf ("%lld", f[n]); return 0; }
  • 2021年10月22日