洛谷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) imes 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) imes 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
", f[n]);
    return 0;
}
原文地址:https://www.cnblogs.com/quanjun/p/12297376.html