P11678 [USACO25JAN] Watering the Plants P 题解
Description
Bessie 的花园中有 \(N\) 株植物,从左到右编号为 \(1\) 到 \(N\)(\(2\leq N\leq 5\cdot 10^5\))。Bessie 知道植物 \(i\) 至少需要 \(w_i\)(\(0\leq w_i \leq 10^6\))单位的水。
Bessie 有一个十分古怪的灌溉系统,包含 \(N-1\) 条水渠,编号为 \(1\) 到 \(N-1\)。每条水渠 \(i\) 有一个相关的单位费用 \(c_i\)(\(1\le c_i\le 10^6\)),Bessie 可以支付费用 \(c_i k\) 来为植物 \(i\) 和 \(i+1\) 各提供 \(k\) 单位的水,其中 \(k\) 是一个非负整数。
Bessie 很忙,可能没有时间使用所有的水渠。对于每一个 \(2\leq i \leq N\),计算仅使用前 \(i-1\) 条水渠灌溉植物 \(1\) 到 \(i\) 所需要的最小费用。
Solution
首先这类题有两种做法,一种是贪心/反悔贪心,另一种是 dp。由于这题条件很好刻画,所以考虑 dp。
容易想到设 \(f_{i,j}\) 表示把 \(c_{[1,i-1]}\) 的状态确定,让 \([1,i-1]\) 的植物已经满足条件,并且第 \(i\) 个植物还需要 \(j\) 单位的水的最小代价。转移代码如下:
memset(f[o], 0x3f, sizeof(f[o]));
for (int j = 1; j <= 200; ++j) chkmin(f[o ^ 1][j], f[o ^ 1][j - 1]);
for (int j = 0; j <= 200; ++j) f[o ^ 1][j] += c[i - 1] * j;
for (int j = 200; ~j; --j) chkmin(f[o ^ 1][j], f[o ^ 1][j + 1]);
for (int j = 0; j <= a[i]; ++j) f[o][j] = f[o ^ 1][a[i] - j];
具体含义就是先取前缀 min,再让 \(f_{i-1,j}\leftarrow f_{i-1,j}+c_{i-1}\cdot j\),此时 \(f_{i-1,j}\) 的意义是 \(c_{i-1}\) 恰好用 \(j\) 次的最小代价,再取一遍后缀 min 后,在 \([0,a_i]\) 值域内翻转就得到了 \(f_{i,*}\) 的状态。
经过打表会发现 \(i\geq 3\) 时 \(f_{i,*}\) 在做完任何操作后都是凸的,所以直接用 deque 维护差分数组即可,全局翻转时打个标记即可。
时间复杂度:\(O(n)\)。
Code
#include <bits/stdc++.h>
#define int int64_t
const int kMaxN = 5e5 + 5;
int n, v = 1e6;
int a[kMaxN], c[kMaxN];
inline void chkmax(int &x, int y) { x = (x > y ? x : y); }
inline void chkmin(int &x, int y) { x = (x < y ? x : y); }
struct Queue {
int f0, dir, sumd, sumc, opd, detd;
// dir = 0 : 正向,dir = 1 : 反向
// sumd : d 的和,sumc : c 的和
// [d, c] 的实际值是 d * opd + detd
std::deque<std::pair<int, int>> q;
// [d, c] : [差分, 数量]
void init() {
f0 = c[1] * std::max(a[1], a[2]);
dir = 0, opd = 1, detd = 0;
if (a[1] < a[2]) {
q.emplace_back(-c[1], a[2] - a[1]);
q.emplace_back(0, v - (a[2] - a[1]));
sumd = -c[1] * (a[2] - a[1]), sumc = v;
} else {
q.emplace_back(0, v);
sumd = 0, sumc = v;
}
}
void fix() { // 末尾加 0
if (dir == 0) {
if (sumc < v) q.emplace_back(-detd * opd, v - sumc);
sumd += -detd * opd * (v - sumc), sumc += v - sumc;
} else {
if (sumc < v) q.emplace_front(-detd * opd, v - sumc);
sumd += -detd * opd * (v - sumc), sumc += v - sumc;
}
}
void getpremin() {
assert(sumc == v);
if (dir == 0) {
for (; !q.empty() && q.back().first * opd + detd >= 0; q.pop_back()) {
auto [d, c] = q.back();
sumd -= d * c, sumc -= c;
}
fix();
} else {
for (; !q.empty() && q.front().first * opd + detd >= 0; q.pop_front()) {
auto [d, c] = q.front();
sumd -= d * c, sumc -= c;
}
fix();
}
}
void getsufmin() {
assert(sumc == v);
if (dir == 0) {
for (; !q.empty() && q.front().first * opd + detd <= 0; q.pop_front()) {
auto [d, c] = q.front();
sumd -= d * c, sumc -= c, f0 += c * (d * opd + detd);
}
if (sumc < v) q.emplace_front(-detd * opd, v - sumc);
sumd += -detd * opd * (v - sumc), sumc += v - sumc;
} else {
for (; !q.empty() && q.back().first * opd + detd <= 0; q.pop_back()) {
auto [d, c] = q.back();
sumd -= d * c, sumc -= c, f0 += c * (d * opd + detd);
}
if (sumc < v) q.emplace_back(-detd * opd, v - sumc);
sumd += -detd * opd * (v - sumc), sumc += v - sumc;
}
}
void addtag(int v) { detd += v; }
void reverse() { f0 += sumd * opd + sumc * detd, dir ^= 1, opd *= -1, detd *= -1; }
void del(int x) {
assert(sumc == v);
if (dir == 0) {
for (; !q.empty() && sumc > x;) {
auto [d, c] = q.back();
int t = std::min(sumc - x, c);
sumd -= d * t, sumc -= t, c -= t;
q.pop_back();
if (c) q.emplace_back(d, c);
}
assert(sumc == x);
reverse(), fix();
} else {
for (; !q.empty() && sumc > x;) {
auto [d, c] = q.front();
int t = std::min(sumc - x, c);
sumd -= d * t, sumc -= t, c -= t;
q.pop_front();
if (c) q.emplace_front(d, c);
}
assert(sumc == x);
reverse(), fix();
}
}
} q;
void dickdreamer() {
std::cin >> n;
for (int i = 1; i <= n; ++i) std::cin >> a[i];
for (int i = 1; i < n; ++i) std::cin >> c[i];
q.init();
std::cout << q.f0 << '\n';
for (int i = 3; i <= n; ++i) {
q.addtag(c[i - 1]), q.getsufmin(), q.del(a[i]), q.getpremin();
std::cout << q.f0 << '\n';
}
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
// std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

浙公网安备 33010602011771号