fireworks题解
10.17
思路:
首先设dp[i][j]为到放第i个烟花的时候站在j的位置可以获得的最大快乐值。
那么我们可以很容易写出转移方程:
dp[i][j] = max(dp[i - 1][k]) + b[i] - |a[i] - j|,其中max(1, j – t * d)<=k<=min(n, j+t * d) 。
不过我们可以发现b[i]是固定的,那么我们转化为求所有|a[i] - x|的最小值,即dp[i][j]表示到第i个烟花的时候站在j的位置可以获得的最小的累加值,转移方程:
dp[i][j] = min(dp[i - 1][k]) + |a[i] - j|,其中max(1, j – t * d)<=k<=min(n, j+t * d)。
由于是求一段区间的最小值,我们可以想到用单调队列维护,维护一个单调升的队列。
不过这题有一点不同的是对于当前考虑的位置i来说其右端的点也需要考虑是否进入队列,假设当前考虑位置i,所需维护区间长度为l,如果i +l<=n,那么看他是否能丢进队列。
一开始考虑的是外层枚举每一个烟花,然后发现区间就是要变化的,果断套了一个线段树上去,T的稳稳的hhh。。。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <climits>
#define LL long long
using namespace std;
const LL INF=0x3f3f3f3f;
int n, m, d;
LL dp[2][150005],a[305],b[305],t[305];
int q[150005], head, tail;
inline int read() {
int x=0;char c=getchar();
while (c<'0'||c>'9') c=getchar();
while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x;
}
int main() {
freopen("fireworks.in", "r", stdin);
freopen("fireworks.out", "w", stdout);
n = read(), m = read(), d = read();
for (int i=1; i<=m; ++i) {
a[i] = read(), b[i] = read(), t[i] = read();
}
int cur=0, pre;
for (int j=1; j<=m; ++j) {
head = tail = 0 , pre = cur, cur ^= 1;
memset(dp[cur], -INF, sizeof(dp[cur]));
int lim = min(1ll * d * (t[j] - t[j-1]), (LL)n);
for (int p=1; p<lim; ++p) {
while (head<tail && dp[pre][q[tail-1]]<=dp[pre][p]) --tail;
q[tail++] = p;
}
LL lf = -1ll * d * (t[j] - t[j-1]) + 1;
LL rg = +1ll * d * (t[j] - t[j-1]) + 1;//枚举的左右边界
for (register int i=1; i<=n; ++i) {
int st = max(lf, 1LL), ed = min(rg, (LL)n);
while (head<tail && dp[pre][q[tail-1]]<=dp[pre][ed]) --tail;
q[tail++] = ed;
while (head<tail && q[head]<st) ++head;
dp[cur][i] = dp[pre][q[head]] + b[j] - abs(a[j]-i);
++lf, ++rg;//维护下一个pos
}
}
LL ans = -INT_MAX;
for (register int i=1; i<=n; ++i) ans = max(dp[cur][i], ans);
cout << ans << endl;
return 0;
}