题目大意
长度为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;
}