洛谷P6040 「ACOI2020」课后期末考试滑溜滑溜补习班 题解 单调队列优化DP

题目链接:https://www.luogu.com.cn/problem/P6040

解题思路:

设状态 \(f[i]\) 表示老师走到第 \(i\) 个人的最小花费,则:

\[f[i] = \min_{j \in [i-x, i-1]}(f[j] + (i-j-1) \times k + d) \]

暴力的方案是 \(O(n \cdot x)\) 时间复杂度,必然超时。

所以考虑单调队列优化,维护一个单调递增的单调队列。

比较的规则是,如果队列当中存在两个坐标 \(j1, j2\)\(j1 \lt j2\),则对于任意一个 \(\gt j1,j2\)\(i\) 来说:

  • \(j1\)\(i\) 所需要的花费是 \((i-1-j1) \cdot k + d\)
  • \(j2\)\(i\) 所需要的花费是 \((i-1-j2) \cdot k + d\)

而两者的花费只差是 \((j2-j1) \cdot k\)

也就是说,既然队列是单调递增的,则对于 \(j1 \lt j2\) 来说,必然存在

\[f[j1] + (j2-j1) \times k \lt f[j2] \]

实现代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 10000010;
typedef long long ll;
int n, k, d, x, tp, a[maxn], Seed;
ll f[maxn];
deque<int> que;
inline int rnd () {
	static const int MOD = 1e9;
	return Seed = ( 1LL * Seed * 0x66CCFF % MOD + 20120712 ) % MOD;
}
int main() {
    scanf("%d%d%d%d%d", &n, &k, &d, &x, &tp);
    if (tp == 0) for (int i = 1; i <= n; i ++) scanf("%d", a+i);
    else {
        scanf("%d", &Seed);
        for (int i = 1; i <= n; i ++) a[i] = rnd();
    }
    f[1] = a[1];
    for (int i = 2; i <= n; i ++) {
        while (!que.empty()) {
            int j = que.back();
            if (f[j] + (ll)(i-1-j) * d >= f[i-1]) que.pop_back();
            else break;
        }
        que.push_back(i-1);
        if (que.front() < i-x) que.pop_front();
        f[i] = f[que.front()] + a[i] + k + (ll)(i - que.front() - 1) * d;
    }
    printf("%lld\n", f[n]);
    return 0;
}
posted @ 2020-02-11 23:18  quanjun  阅读(150)  评论(0编辑  收藏  举报