CF372C Watching Fireworks is Fun

传送门

一道有趣的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;
}
原文地址:https://www.cnblogs.com/ASTiKi/p/12285911.html