一道有趣的
DP
题目大意
城镇中有$n$个位置,有$m$个烟花要放。第$i$个烟花放出的时间记为$t_{i}$,放出的位置记为$a_{i}$。如果烟花放出的时候,你处在位置$x$,那么你将收获$b_{i}- \left | a_{i}-x \right |$点快乐值。
初始你可在任意位置,你每个单位时间可以移动不大于$d$个单位距离。现在你需要最大化你能获得的快乐值。
思路:DP+单调队列+滚动数组
有一个显然的动态转移方程:
$f_{i,j}=max \left (f_{i,j},f_{i-1,k}+b_{i}- \left |a_{i}-j \right | \right )$
$f_{i,j}$表示在位置$j$放第$i$个烟花获得的最大值。
则$ans=max_{i=1}^{n}f_{m, i}$。
观察方程,可以看出$b_{i}$可以提出来,而$- \left |a_{i}-j \right |$的值在计算的过程中也是确定的,也可提出来。
最后的式子长这个样子
$f_{i,j}=min \left (f_{i,j},f_{i-1,k} \right )$
$ans=sum_{i=1}^{m}b_{i}-min_{i=1}^{n}f_{m, i}$
感觉可以,一看复杂度$O \left ( n^{2}m \right )$,T到飞起。
从上式中,可以看出$k$的范围是
$j- \left ( t_{i}-t_{i-1} \right ) *d \leq k \leq j+ \left ( t_{i}-t_{i-1} \right ) *d$
如何维护$k$的值?很明显,用单调队列维护,使得其能在均摊$0 \left (1 \right )$的时间复杂度内计算出$min \left (f_{i,j},f_{i-1,k} \right )$,
没学的赶快学
。
于是复杂度从$O \left ( n^{2}m \right )$降到了$O \left ( nm \right )$。
一看范围,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\n", sum - ans); return 0; }