这是一道经典的单调队列优化DP.
首先不难看出要用动态规划求解,dp[i][j]表示第i个工人粉刷第j块木板时的最大收益,由此
dp[i][j] = max(
dp[i-1][j] 第i个工人不刷木板
dp[i-1][k] + (j - k) * p 第i个工人从第k+1块木板开始一直刷到第j块
)
由于每个工人粉刷的木板必须包含s,因此dp[i][…]只跟dp[i-1][…]有关。
另外前两个方程已经是O(1)时间的,而第三个方程可以变为
max(dp[i-1][k] - k * p) + j * p,因此我们将括号内的数值用一个单调队列维护,然后枚举i,j,k即可。
维护单调队列时我们保证val(dp[i-1][k] - k * p)从大到下,pos从小到大,从队尾不断删除比要插入的小的值,查询时从队首不断抛出,第一个满足位置区间的即是最大的满足条件的val。
注意k是已经粉刷完毕的最后一块木板,k+1是该工人需要粉刷的第一块木板,而j是最后一块需要粉刷的木板:
s - l + 1 <= k + 1 <= s
s - l <= k < s
s <= j <= j + l - 1
搞定需要枚举的区间后剩下的就很简单了。
#include <cstdio>
#include <deque>
#include <algorithm>
using namespace std;
const int maxn = 160000 + 10;
const int maxm = 100 + 10;
struct worker {
int l, p, s;
bool operator < (const worker& w) const {
return s < w.s;
}
};
worker a[maxm];
int dp[maxm][maxn];
// dp[i][j] = dp[i-1][k] + (j-k) * p
int main(int argc, char const *argv[]) {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d%d%d", &a[i].l, &a[i].p, &a[i].s);
}
sort(a + 1, a + m + 1);
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j] = dp[i-1][j];
}
//first == val, second == pos
deque<pair<int, int> > deq;
for (int k = max(a[i].s - a[i].l, 0); k < a[i].s; k++) {
int val = dp[i-1][k] - k * a[i].p;
while (!deq.empty() && deq.back().first <= val) {
deq.pop_back();
}
deq.push_back(make_pair(val, k));
}
for (int j = a[i].s; j < a[i].s + a[i].l; j++) {
while (!deq.empty() && deq.front().second < j - a[i].l) {
deq.pop_front();
}
dp[i][j] = max(dp[i][j],deq.front().first + j * a[i].p);
}
}
printf("%d\n", *max_element(dp[m] + 1, dp[m] + n + 1));
return 0;
}