AT_arc180_d [ARC180D] Division into 3
题意:
给你一个长度为 \(N\) 的整数序列 \(A=(A_1,A_2,\cdots,A_N)\) 。请回答下列 \(Q\) 个问题。
- \(i\) -th 查询:给定整数 \(L_i\) 和 \(R_i\) 。求解下面 \(B=(A_{L_i},A_{L_i+1},\cdots,A_{R_i})\) 的问题。
- 将 \(B\) 分成三个非空的连续子序列。对于每个连续子序列,让我们找出其元素的最大值。求这些最大值之和的最小值。在这里,问题的限制条件迫使 \(B\) 的长度至少为 \(3\) ,因此总有至少一种方法将其分成三个非空的连续子序列。
分析:
设 \(A,B,C\) 分别表示依次划分的三段区间,对于整个序列的最大值 \(mx\),显然 \(A,B,C\) 的代价至少有一个等于 \(mx\)。分类讨论拆解这个问题。
\(A=mx\):
不妨假设 \(B\) 到 \(C\) 的分界点被固定,因为 \(A\) 的代价已经确定,那么 \(B\) 长度越小,\(B\) 的代价就越小,总代价就越小。因此 \(B\) 的长度恰好为 \(1\)。记 \(x\) 表示 \(\min_{i=l}^{r}i[a_{i}=mx]\),那么总代价即为:
记 \(f_{i}=a_{i}+\max_{j=i+1}^{r}a_{j}\),使用扫描线维护固定 \(r\) 时,所有的 \(f_{i}\)(\(1\le i < r\))。
配合单调栈使用,当 \(r \leftarrow r+1\) 时,假设新加入单调栈的 \(a_{r}\) 弹出了 \(s_{top}\)(\(a_{r}\ge a_{s_{top}}\)),那么在线段树上就是 \(f_{i} \leftarrow f_{i}-a_{s_{top}}+a_{r}\),其中 \(i \in [s_{top-1},s_{top})\)。然后再单独更新一下 \(f_{r-1}\)。
\(B=mx\):
不难发现此时 \(A,C\) 的长度为 \(1\) 时,一定是最优的,因为 \(A\) 的左端点,\(B\) 的右端点都固定了,长度越小,代价越小。这是区间 RMQ 问题。ST 表或线段树就能解决。
\(C=mx\):
与 \(A=mx\) 是对称问题,序列翻转后再使用 \(A=mx\) 的算法即可。
时间复杂度 \(O((n+Q) \log n)\),代码虽然长但不用动脑子。
代码:
#include<bits/stdc++.h>
#define int long long
#define N 300005
using namespace std;
int n, Q;
int a[N], L[N], R[N], ans[N];
struct Tree1 { //区间加; 区间min
int c[N * 4], tag[N * 4];
void clear() {
memset(c, 0, sizeof(c));
memset(tag, 0, sizeof(tag));
}
void pushup(int u) {
c[u] = min(c[u * 2], c[u * 2 + 1]);
}
void maketag(int u, int x) {
c[u] += x;
tag[u] += x;
}
void pushdown(int u) {
if(!tag[u]) return;
maketag(u * 2, tag[u]);
maketag(u * 2 + 1, tag[u]);
tag[u] = 0;
}
void update(int u, int L, int R, int l, int r, int x) {
if(l <= L && R <= r) {
maketag(u, x);
return;
}
if(R < l || r < L) return;
int mid = (L + R) / 2;
pushdown(u);
update(u * 2, L, mid, l, r, x);
update(u * 2 + 1, mid + 1, R, l, r, x);
pushup(u);
}
int query(int u, int L, int R, int l, int r) {
if(l <= L && R <= r) return c[u];
if(r < L || R < l) return 1e15;
int mid = (L + R) / 2;
pushdown(u);
return min(query(u * 2, L, mid, l, r), query(u * 2 + 1, mid + 1, R, l, r));
}
}t1;
struct node {
int id, Max;
friend node operator + (node A, node B) {
if(A.Max > B.Max) return A;
else if(A.Max < B.Max) return B;
else return ((node){min(A.id, B.id), A.Max});
}
};
struct Tree2 { //区间max
node c[N * 4];
void pushup(int u) {
c[u] = c[u * 2] + c[u * 2 + 1];
}
void build(int u, int L, int R) {
if(L == R) {
c[u].id = L;
c[u].Max = a[L];
return;
}
int mid = (L + R) /2;
build(u * 2, L, mid);
build(u * 2 + 1, mid + 1, R);
pushup(u);
}
node query(int u, int L, int R, int l, int r) {
if(l <= L && R <= r) return c[u];
if(r < L || R < l) return (node){0, (int)-1e15};
int mid = (L + R) / 2;
return query(u * 2, L, mid, l, r) + query(u * 2 + 1, mid + 1, R, l, r);
}
}t2;
vector<int>p[N];
int S[N], top;
void Sol1() { //解决 A=mx 或 C=mx 的问题
t1.clear();
S[0] = 1; top = 0;
for(int i = 1; i <= n; i++) p[i].clear();
for(int i = 1; i <= Q; i++) p[R[i]].push_back(i);
for(int i = 1; i <= n; i++) {
while(top && a[i] >= a[S[top]]) {
t1.update(1, 1, n, S[top - 1], S[top] - 1, -a[S[top]] + a[i]);
top--;
}
S[++top] = i;
if(i > 1) t1.update(1, 1, n, i - 1, i - 1, a[i - 1] + a[i]);
for(auto Pos : p[i]) {
node Get = t2.query(1, 1, n, L[Pos], R[Pos]);
int x = Get.id;
//cout << Get.Max << endl;
//for(int z = x + 1; z <= i - 1; z++) cout << z << " : " << t1.query(1, 1, n, z, z) << endl;
if(x + 1 <= i - 1) ans[Pos] = min(ans[Pos], Get.Max + t1.query(1, 1, n, x + 1, i - 1));
}
}
}
void Sol2() { //解决 B=mx 的问题
for(int i = 1; i <= Q; i++)
ans[i] = min(ans[i], a[L[i]] + a[R[i]] + t2.query(1, 1, n, L[i] + 1, R[i] - 1).Max);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> Q;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= Q; i++) {
cin >> L[i] >> R[i];
ans[i] = 1e18;
}
t2.build(1, 1, n);
Sol1();
Sol2();
reverse(a + 1, a + n + 1);
for(int i = 1; i <= Q; i++) {
int l = L[i], r = R[i];
L[i] = n - r + 1;
R[i] = n - l + 1;
}
t2.build(1, 1, n);
Sol1();
for(int i = 1; i <= Q; i++) cout << ans[i] << endl;
return 0;
}
/*
5 1
4 3 1 1 4
1 5
*/
/*
7 1
4 3 1 1 4 5 2
1 7
*/