题意
现在有 n 个工人要粉刷一段篱笆,每个工人可以粉刷包含 starti 的一段最长不超过 leni 的篱笆(当然也可以不粉刷),每个工人粉刷长度为 1 的篱笆的收益为 costi,求最大收益
思路
考虑 dp,设 dpi,j 表示前 i 个人粉刷前 j 段篱笆可以获得的最大收益
显然有如下的暴力转移
dpi,jdpi,jdpi,j←min{dpi−1,k+(j−k)×costi},←dpi−1,j,←dpi,j−1,(让第 i 个人粉刷区间 (k,j]),(第 i 个人不粉刷),(第 i 个人不粉刷).
观察发现,瓶颈在于如何快速求出 min{dpi−1,k+(j−k)×costi}
对上式进行变形,可得
min{dpi−1,k+(j−k)×costi}==min{dpi−1,k+j×costi−k×costi}min{dpi−1,k−k×costi}+j×costi
即上式可以被分为两个部分,仅包含 i,j 的和仅包含 i,k 的部分,我们只需要求出 min{dpi−1,k−k×costi} 部分的最值即可
那么显然,在循环每个 i 时,可以更新 dpi,j 的 k 是在变化的,我们使用单调队列维护 min{dpi−1,k−k×costi},然后 O(1) 更新即可
均摊时间复杂度为 O(nm)
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <bits/stdc++.h> namespace solution { typedef long long LL; const int MAXN = 100 + 5; const int MAXM = 2e5 + 5; int n, m; struct Node { int start, len, cost; }; bool operator<(const Node &a, const Node &b) { return a.start < b.start; } Node node[MAXN]; int dp[MAXN][MAXM]; int main() { scanf("%d%d", &m, &n); for (int i = 1; i <= n; i++) { scanf("%d%d%d", &node[i].len, &node[i].cost, &node[i].start); } std::sort(node + 1, node + n + 1); for (int i = 1; i <= n; i++) { std::deque<std::pair<int, int>> q; q.push_back(std::make_pair(0, 0)); for (int j = 1; j <= m; j++) { dp[i][j] = std::max(dp[i - 1][j], dp[i][j - 1]); while (!q.empty() && q.front().second < j - node[i].len) { q.pop_front(); } if (!q.empty() && node[i].start <= j && j <= node[i].start + node[i].len - 1) { dp[i][j] = std::max(dp[i][j], q.front().first + j * node[i].cost); } if (j < node[i].start) { while (!q.empty() && q.back().first <= dp[i - 1][j] - node[i].cost * j) { q.pop_back(); } q.push_back(std::make_pair(dp[i - 1][j] - node[i].cost * j, j)); } } } printf("%d\n", dp[n][m]); return 0; } } int main() { solution::main(); return 0; }
|