洛谷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;
}