一道有趣的DP
题目大意
城镇中有 $n$ 个位置,有 $m$ 个烟花要放。第 $i$ 个烟花放出的时间记为 $t_{i}$ ,放出的位置记为 $a_{i}$ 。如果烟花放出的时候,你处在位置 $x$,那么你将收获 $b_{i}- left | a_{i}-x ight |$ 点快乐值。
初始你可在任意位置,你每个单位时间可以移动不大于$d$个单位距离。现在你需要最大化你能获得的快乐值。
思路:DP+单调队列+滚动数组
有一个显然的动态转移方程:
$$
f_{i,j}=max left (f_{i,j},f_{i-1,k}+b_{i}- left |a_{i}-j
ight |
ight )
$$
$f_{i,j}$ 表示在位置 $j$ 放第 $i$ 个烟花获得的最大值。
则 $ans=max_{i=1}^{n}f_{m, i}$。
观察方程,可以看出 $b_{i}$ 可以提出来,而 $- left |a_{i}-j ight |$ 的值在计算的过程中也是确定的,也可提出来。
最后的式子长这个样子
$$
f_{i,j}=min left (f_{i,j},f_{i-1,k}
ight )
//
ans=sum_{i=1}{m}b_{i}-min_{i=1}{n}f_{m, i}
$$
感觉可以,一看复杂度 $O left ( n^{2}m ight )$,T 到飞起。
从上式中,可以看出 $k$ 的范围是
$$
j- left ( t_{i}-t_{i-1}
ight ) *d leq k leq j+ left ( t_{i}-t_{i-1}
ight ) *d
$$
如何维护 $k$ 的值?很明显,用单调队列维护,使得其能在均摊 $0 left (1 ight )$ 的时间复杂度内计算出 $min left (f_{i,j},f_{i-1,k} ight )$。
于是复杂度从 $O left ( n^{2}m ight )$ 降到了 $O left ( nm ight )$。
一看范围,150000。。。直接爆炸。
仔细观察,发现可以用滚动数组优化空间,能过。
代码
#include <iostream>
#define RI register int
typedef long long ll;
const int N = 150001;
using namespace std;
template <class T>
inline void read(T &x) {
T f = 1; x = 0; char c = getchar();
while(c > '9' || c < '0') {
if(c == '-')
f = -f;
c = getchar();
}
while(c >= '0' && c <= '9') {
x = x * 10 + c - '0';
c = getchar();
}
x *= f;
}
int n, m, d, t[2];
ll f[2][N], ans = 1e18 + 7, sum;
int l, r, q[N];
int p;
inline int abs(int x) {
return x > 0 ? x : -x;
}
inline ll min(ll x, ll y) {
return x > y ? y : x;
}
int main() {
read(n), read(m), read(d);
for(RI i = 1; i <= m; i++) {
int a, b;
read(a), read(b), read(t[p ^ 1]);
sum += b;
ll len = ll(t[p ^ 1] - t[p]) * d;
l = 1, r = 0;
for(RI j = 1; j <= n; j++) {
while(l <= r && q[l] < j - len)
l++;
while(l <= r && f[p][q[r]] > f[p][j])
r--;
q[++r] = j;
f[p ^ 1][j] = f[p][q[l]] + abs(a - j);
}
l = 1, r = 0;
for(RI j = n; j >= 1; j--) {
while(l <= r && q[l] > j + len)
l++;
while(l <= r && f[p][q[r]] > f[p][j])
r--;
q[++r] = j;
f[p ^ 1][j] = min(f[p][q[l]] + abs(a - j), f[p ^ 1][j]);
}
p ^= 1;
}
for(RI i = 1; i <= n; i++)
ans = min(ans, f[p][i]);
printf("%lld
", sum - ans);
return 0;
}