题目大意
长度为n的墙,k个粉刷匠。第 i 个粉刷匠在 s[i] 块木板前,他最多可以刷包含 s[i] 的长度为 l[i] 的区间,他刷单位长度获得钱 p[i] 。求k个粉刷匠最多能赚多少钱?
递归式的产生
动规首先要有方向,所以把所有的粉刷匠根据s[i]排序。影响赚钱最大值的因素有选了哪几个人,以及人是怎么粉刷的。所以定义DP[i][j]为前i个粉刷匠负责到木板j时赚钱最大值。分情况:1.j没有被i刷(1)i什么木板都没刷(①)(2)i刷了一些木板,但没有刷到j(②)。2.j被i刷(③)。
定义k为粉刷匠i-1所刷到的最后一个木板的位置,则总递归式为:
DP[i][j] = max{①DP[i-1][j], ②DP[i][j-1], ③if(j>=S[i]) max foreach k(max(0, j-L[i])<=k<=S[i]-1) (DP[i-1][k] + P[i] * (j-k))}。
单调队列优化
当i固定时,我们要对每一个j,在一个已知区间[max(0, j-L[i]), S[i]-1]中求最值,区间随着j的增大在从左往右滑动,所以想到对每个i构造单调队列。队列中单调的只能有k,于是提出P[i]*j,将③变为P[i]*j+max{DP[i-1][k]+P[i]-k}。这样维护一个关于k,DP[i-1][k]+P[i]-k单调递减的单调序列即可达到优化的效果。
注意
- k值不应当定义为粉刷匠i从第k个木板开始刷,因为这样单调队列里的值关于k-1单调,翻来倒去导致了混乱。
- 看以下代码:
for (int k2 = max(0, s - len); k2 <= s; k2++) { int k1 = q.front(); while (!q.empty() && DP[i - 1][k1] - p*k1 <= DP[i - 1][k2] - p*k2) q.pop_front(); q.push_back(k2); }
//s:s[i] p:p[i]这里错误非常多:
- 此处k1永远是循环刚开始的q.front(),此后一直都不变。
- k2<=s:循环轮到s-1时,s-1已被处置,不需要多处理一次s-1+1。
- 本循环是在处置队尾,所以是q.pop_back(),而不是q.pop_front()。
完整代码:
#include <cstdio> #include <cstring> #include <algorithm> #include <cstdarg> using namespace std; #define LOOP(i,n) for(int i=1; i<=n; i++) void _printf_(const char * format, ...) { #ifdef _DEBUG //va_list args; //va_start(args, format); //vprintf(format, args); //va_end(args); #endif } const int MAX_BLOCK = 17000, MAX_WORKER = 110; int DP[MAX_WORKER][MAX_BLOCK]; int totWorker, totBlock; struct IntDeque { int a[MAX_BLOCK], head, tail; void clear() { head = tail = 0; } void push_back(int x) { a[tail++] = x; } void pop_back() { tail--; } void pop_front() { head++; } bool empty() { return head == tail; } int front() { return a[head]; } int back() { return a[tail-1]; } void Tranvas() { for (int i = head; i < tail; i++)_printf_("%d ", a[i]); _printf_(" "); } }; struct Worker { int Start, Price, Len; bool operator <(const Worker a)const { return Start < a.Start; } }_workers[MAX_WORKER]; int Dp() {//i:worker j:block memset(DP, 0, sizeof(DP)); static IntDeque q; LOOP(i, totWorker) { q.clear(); int p = _workers[i].Price, s = _workers[i].Start, len = _workers[i].Len; for (int k2 = max(0, s - len); k2 <= s -1; k2++) { int k1 = 0; while (!q.empty() && DP[i - 1][k1=q.back()] - p*k1 <= DP[i - 1][k2] - p*k2) q.pop_back(); q.push_back(k2); } LOOP(j, totBlock) { DP[i][j] = max(DP[i - 1][j], DP[i][j - 1]); //q.Tranvas(); _printf_("DP[%d][%d]=%d ", i, j, DP[i][j]); if (j >= s) { int len = j - _workers[i].Len; while (!q.empty() && q.front() < len) q.pop_front(); int k = q.front(); if (!q.empty()) DP[i][j] = max(DP[i][j], DP[i - 1][k] + p*(j - k)); //q.Tranvas(); _printf_("DP[%d][%d]=%d ", i, j, DP[i][j]); } } } return DP[totWorker][totBlock]; } int main() { #ifdef _DEBUG freopen("c:\noi\source\input.txt", "r", stdin); #endif scanf("%d%d", &totBlock, &totWorker); LOOP(i, totWorker) scanf("%d%d%d", &_workers[i].Len, &_workers[i].Price, &_workers[i].Start); sort(_workers + 1, _workers + totWorker + 1); printf("%d ", Dp()); return 0; }