产品销售
又是很好的线段树题.
机房的机器, 为什么我在别的地方输的都是正常的全角字符, 到了 CSDN 就变成了只能输出半角字符…
大致思路
不是很直接的贪心题, 应当先对题目进行抽象建模.
- S → i S \rightarrow i S→i 连边 ( D i , 0 ) (D_i, 0) (Di,0), 第 i i i 天有 D i D_i Di 个购买需求.
- i → T i \rightarrow T i→T 连边 U i , P i U_i, P_i Ui,Pi, 第 i i i 天的生产能力.
- 然后是这一天需求的可以在之前的时间点生产, 也可能在之后的时间点生产.
- i + 1 → i i+1 \rightarrow i i+1→i 连边 M i M_i Mi, i → i + 1 i \rightarrow i+1 i→i+1 连边 C i C_i Ci.
注意到处理 D i D_i Di 的顺序并不影响最终答案, 所以可以考虑从左往右一次处理.
第 i i i 天的购买需求显然必然会被全部处理. 它可能在之前, 此刻, 之后被处理.
注意到, 由于权值为正, 如果从 i i i 流到 i − 1 i-1 i−1 再流回 i i i, 肯定不优.
根据以上处理方式, i → i − 1 i \rightarrow i-1 i→i−1 的边不可能被被退流, i → i + 1 i \rightarrow i+1 i→i+1 的边可能会被退流.
从 i i i 流到 i + 1 i+1 i+1, 它的代价一定是 C i × f l o w C_i \times flow Ci×flow. 而从 i + 1 i+1 i+1 流到 i i i, 因为退流的代价为 − C i × f l o w < 0 -C_i \times flow < 0 −Ci×flow<0, 而普通的代价为 M i > 0 M_i > 0 Mi>0, 如果能够退流, 优先退流; 不能退流时, 才会走 M i M_i Mi.
设当前这一部分流量 f l o w flow flow 流入汇点之前到达的节点为 p p p, 即通过 p → T p \rightarrow T p→T 流入 T T T.
- 向左走, 如果有反悔边可走, 必然走反悔边, 因为反悔边代价为负.
- 反悔边的流量在处理完它的左端点的时候, 已经不可能继续增加了.
- 而消耗反悔边, 又是从处理到它的右端点的时候开始的.
- 考虑维护反悔边还剩多少.
- 如果 p < i p < i p<i, 那么 [ p , i − 1 ] [p, i-1] [p,i−1] 的所有反悔边的流量都要减少本轮的流量 f l o w flow flow.
- 那么流量在这一轮被减到 0 0 0 的所有位置需要被找出来, 然后 “身份转变”, 由反悔边变成普通边.
- 向右走的话, 那么 [ i , p − 1 ] [i, p-1] [i,p−1] 的反悔边的流量加上 f l o w flow flow.
注意: 这里边的编号是它的左端点的编号.
线段树
具体来说, 需要建两只线段树, 而且两只维护的东西不同. (代码量一下子上来了.)
第一只线段树
第一个线段树的区间是
[
1
,
n
]
[1, n]
[1,n], 维护点的信息. 第
p
p
p 个位置的值是以当前第
i
i
i 天的 一个 需求被放到第
p
p
p 天解决的花费, 即
S
→
i
→
⋯
→
p
→
T
S \rightarrow i \rightarrow \cdots \rightarrow p \rightarrow T
S→i→⋯→p→T 的花费.
在第
i
i
i 天的
D
i
D_i
Di 个需求全部解决之后, 考虑怎么将线段树的信息转移为第
i
+
1
i+1
i+1 天的信息.
- 对于 [ 1 , i ] [1, i] [1,i], 如果存在反悔 i → i + 1 i \rightarrow i+1 i→i+1 的边, 那么区间加 − C i -C_i −Ci, 否则区间加 M i M_i Mi.
- 对于 [ i + 1 , n ] [i+1, n] [i+1,n], 撤掉 i → i + 1 i \rightarrow i+1 i→i+1 的代价, 区间加 − C i -C_i −Ci.
第一个线段树还需要支持查找全局最小值, 以及全局最小值的位置. 计算单次使用的流量需要知道位置.
考虑到一个点作为出口, 出口这一部分也有流量限制. 这里满流的时候, 直接给代价赋值 + ∞ +\infty +∞.
综上所述, 第一个线段树需要支持的操作: 区间加, 单点赋值 + ∞ +\infty +∞, 查询全局最小值&位置.
第二只线段树
第二个线段树的区间范围是 [ 1 , n − 1 ] [1, n-1] [1,n−1], 维护边的信息.
它需要区间非 0 0 0 数最小值, 区间加正整数, 对非 0 0 0 值区间减正整数, 在区间减正整数之后需要找到新被变成 0 0 0 的位置, 然后把它由反悔边变成普通边.
零和非零还不一样… 一看就不是很好处理的样子…
不要慌. 线段树上除了非 0 0 0 数最小值之外, 还需要维护区间有没有 0 0 0, 区间有没有非 0 0 0.
这样就方便了.
新被变成
0
0
0 的那些区间, 先不改动零和非零的标记, 在寻找新变成
0
0
0 的位置的时候在修改它的标记. 不然的话, 一是没法知道区间还有没有非零 (这个最好需要通过 pushup
来获知), 二是不知道可能具体是那些位置. 而因为它变成
0
0
0 之后就不会再被处理了, 所以时间复杂度正确.
这个找位置的方法比较经典, 但是这里没有必要传 vector
的引用, 直接找到就对另一个线段树做区间修改就好了.
代码
说不明白啊, 看代码算了.
这题一发交到最优解.
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>
#include <random>
#include <utility>
#define fi first
#define se second
using namespace std;
using LL = long long;
using LLL = __int128;
using pii = pair<int, int>;
const int MAXN = 1e5+5;
const int INF = 0x3f3f3f3f;
mt19937 rnd(random_device{}());
template<typename Tp> void read(Tp &res) {
char ch; bool op = 0; res = 0;
do ch = getchar(), op |= ch == '-'; while (ch < '0' || ch > '9');
do res = (res<<3)+(res<<1)+ch-48, ch = getchar(); while (ch>='0' && ch<='9');
if (op) res = -res;
}
// I need two segment trees.
int n, inlim[MAXN], outlim[MAXN], ext[MAXN], lv[MAXN], rv[MAXN];
namespace SGT1 {
struct Node {
pii val;
int tag;
Node(){}
Node(pii v) { val = v, tag = 0; }
void modify(int x) {
if (val.fi != INF) val.fi += x;
tag += x;
}
Node operator+(Node x) {
return Node(min(val, x.val));
}
} s[MAXN<<2];
int ori[MAXN];
void build(int cur, int l, int r) {
if (l == r) return s[cur] = Node(make_pair(ori[l], l)), void();
int mid = (l + r) >> 1;
build(cur<<1, l, mid), build(cur<<1|1, mid+1, r);
s[cur] = s[cur<<1] + s[cur<<1|1];
}
void init() {
int s = 0;
for (int i = 1; i <= n; ++i) {
ori[i] = ext[i] + s;
s += rv[i];
}
build(1, 1, n);
}
void spread(int cur) {
if (!s[cur].tag) return;
s[cur<<1].modify(s[cur].tag), s[cur<<1|1].modify(s[cur].tag);
s[cur].tag = 0;
}
void update(int cur, int l, int r, int L, int R, int v) {
if (L <= l && R >= r) return s[cur].modify(v);
spread(cur);
int mid = (l + r) >> 1;
if (L <= mid) update(cur<<1, l, mid, L, R, v);
if (R > mid) update(cur<<1|1, mid+1, r, L, R, v);
s[cur] = s[cur<<1] + s[cur<<1|1];
}
void fullflow(int cur, int l, int r, int p) {
if (l == r) return s[cur].val.fi = INF, void();
spread(cur);
int mid = (l + r) >> 1;
if (p <= mid) fullflow(cur<<1, l, mid, p);
else fullflow(cur<<1|1, mid+1, r, p);
s[cur] = s[cur<<1] + s[cur<<1|1];
}
}
namespace SGT2 {
struct Node {
int mn, tag;
bool f0, f1;
void modify(int x) {
tag += x;
if (x > 0 && f0) mn = x, f0 = 0, f1 = 1;
else if (f1) mn += x;
else mn = INF;
}
Node operator+(Node x) {
Node res;
res.mn = min(mn, x.mn), res.tag = 0;
res.f0 = f0 | x.f0, res.f1 = f1 | x.f1;
return res;
}
} s[MAXN<<2];
void build(int cur, int l, int r) {
if (l == r) return s[cur] = {INF, 0, 1, 0}, void();
int mid = (l + r) >> 1;
build(cur<<1, l, mid), build(cur<<1|1, mid+1, r);
s[cur] = s[cur<<1] + s[cur<<1|1];
}
void spread(int cur) {
if (!s[cur].tag) return;
s[cur<<1].modify(s[cur].tag), s[cur<<1|1].modify(s[cur].tag);
s[cur].tag = 0;
}
void update(int cur, int l, int r, int L, int R, int v) {
if (L <= l && R >= r) return s[cur].modify(v), void();
spread(cur);
int mid = (l + r) >> 1;
if (L <= mid) update(cur<<1, l, mid, L, R, v);
if (R > mid) update(cur<<1|1, mid+1, r, L, R, v);
s[cur] = s[cur<<1] + s[cur<<1|1];
}
Node query(int cur, int l, int r, int L, int R) {
if (L <= l && R >= r) return s[cur];
spread(cur);
int mid = (l + r) >> 1;
if (R <= mid) return query(cur<<1, l, mid, L, R);
if (L > mid) return query(cur<<1|1, mid+1, r, L, R);
return query(cur<<1, l, mid, L, R) + query(cur<<1|1, mid+1, r, L, R);
}
void fullflow(int cur, int l, int r, int L, int R) {
if (l == r) {
s[cur] = {INF, 0, 1, 0};
SGT1::update(1, 1, n, 1, l, lv[l] + rv[l]);
return;
}
spread(cur);
int mid = (l + r) >> 1;
if (L <= mid && !s[cur<<1].mn) fullflow(cur<<1, l, mid, L, R);
if (R > mid && !s[cur<<1|1].mn) fullflow(cur<<1|1, mid+1, r, L, R);
s[cur] = s[cur<<1] + s[cur<<1|1];
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("lg4217.in", "r", stdin);
freopen("lg4217.out", "w", stdout);
#endif
read(n);
for (int i = 1; i <= n; ++i) read(inlim[i]);
for (int i = 1; i <= n; ++i) read(outlim[i]);
for (int i = 1; i <= n; ++i) read(ext[i]);
for (int i = 1; i < n; ++i) read(lv[i]);
for (int i = 1; i < n; ++i) read(rv[i]);
SGT1::init(), SGT2::build(1, 1, n-1);
LL ans = 0;
for (int i = 1; i <= n; ++i) {
while (inlim[i]) {
int val = SGT1::s[1].val.fi, p = SGT1::s[1].val.se;
if (p < i) {
auto res = SGT2::query(1, 1, n-1, p, i-1);
int cur = min(min(inlim[i], outlim[p]), res.mn);
inlim[i] -= cur, outlim[p] -= cur, ans += 1ll * val * cur;
SGT2::update(1, 1, n-1, p, i-1, -cur);
SGT2::fullflow(1, 1, n-1, p, i-1);
} else {
int cur = min(inlim[i], outlim[p]);
inlim[i] -= cur, outlim[p] -= cur, ans += 1ll * val * cur;
if (p > i) SGT2::update(1, 1, n-1, i, p-1, cur);
}
if (!outlim[p]) SGT1::fullflow(1, 1, n, p);
}
if (i == n) break;
SGT1::update(1, 1, n, 1, i, SGT2::query(1, 1, n-1, i, i).f1 ? -rv[i] : lv[i]);
SGT1::update(1, 1, n, i+1, n, -rv[i]);
}
printf("%lld\n", ans);
return 0;
}