P9530 [JOISC2022] 鱼 2
*P9530 [JOISC2022] 鱼 2
考虑如何判定一条鱼能够活到最后。从它的位置开始,向左向右找到第一条大于它当前重量的鱼。将这两条鱼之间的所有鱼吃掉,不断重复该过程直到它两侧的鱼的重量都大于它。寻找前驱和后继的过程可以通过线段树上二分维护,而每次寻找大于当前重量的鱼的前驱后继时,由于最终一定会吃掉一个前驱或后继(否则无法继续扩展),于是鱼的重量翻倍。因此整个过程的复杂度是
容易想到用下标区间
考虑对一段区间求出区间内每条鱼在当前区间范围内的下标区间
结合 “包含某点的不同区间数量很少” 以及 “只有包含左端点或右端点的区间有用”,可知只需用
将区间根据是否可达左右端点分为三类(两者都不可达的区间无用)。左侧可达左端点但不可达右端点的区间依然可达左端点且不可达右端点,右侧同理。
考虑左侧可达右端点
- 对可达整体右端点
r 的贡献:求出最小的可扩张为整体右端点r 的区间[L_i, M] ,然后不断向左扩展。过程中可能会出现无法扩展的情况,那么上一次无法扩展([L_i, M] )到这一次无法扩展([L_j, M], i < j )之间,所有[L_k, M], i < k\leq j 对应的所有下标都恰好扩张为[L_j, r] 。 - 对可达整体左端点
l 但不可达右端点r 的贡献:求出[l, M] (也是最大的[L_c, M] )可达的右边界r' ,如果右边界不是右端点(r' < r ),那么求出最小的左侧可达r' 的区间[L_i, M] ,所有[L_k, M], i \leq k\leq c 的对应下标都恰好扩张为[l, r'] 。
为了避免 ”求出最小的满足 …… 的区间“ 的二分,可以用双指针
- 如果能向右边扩张就向右边扩张,
pr 加1 ; - 否则如果左边到头了(
pl = c )就退出; - 否则如果左边不能扩张,则说明两边都不能扩张,下标数清零。然后
pl 加1 (无论左边是否能够扩张),并加入[L_{pl}, M] 的对应下标数。
可以在双指针的过程中求出可达整体右端点
对右侧可达左端点
每个
时间复杂度
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 5;
constexpr int inf = 1e9 + 7;
int n, q, a[N];
struct info {int sum, cnt, out;};
struct dat {
int l, r, sum, cnt;
vector<info> L, R;
dat operator + (const dat &z) const {
dat x = {l, z.r, min(inf, sum + z.sum), 0, L, z.R};
vector<info> fl = R, fr = z.L;
fl.push_back({sum, cnt, 0});
fr.push_back({z.sum, z.cnt, 0});
vector<info> apl, apr;
int sum, bound;
// left
int pl = 0, pr = -1, tot = fl[0].cnt;
while(1) {
sum = min(inf, fl[pl].sum + (pr == -1 ? 0 : fr[pr].sum));
bound = (pr == -1 ? z.l : fr[pr].out);
if(sum >= bound && pr + 1 < fr.size()) pr++;
else if(pl == fl.size() - 1) break;
else {
if(sum < fl[pl].out) {
if(pr == fr.size() - 1) apr.push_back({sum, tot, fl[pl].out});
tot = 0;
}
tot += fl[++pl].cnt;
}
}
if(pr == fr.size() - 1) x.cnt += tot;
else x.L.push_back({sum, tot, bound});
swap(fl, fr);
// right
pl = 0, pr = -1, tot = fl[0].cnt;
while(1) {
sum = min(inf, fl[pl].sum + (pr == -1 ? 0 : fr[pr].sum));
bound = (pr == -1 ? r : fr[pr].out);
if(sum >= bound && pr + 1 < fr.size()) pr++;
else if(pl == fl.size() - 1) break;
else {
if(sum < fl[pl].out) {
if(pr == fr.size() - 1) apl.push_back({sum, tot, fl[pl].out});
tot = 0;
}
tot += fl[++pl].cnt;
}
}
if(pr == fr.size() - 1) x.cnt += tot;
else x.R.push_back({sum, tot, bound});
for(info it : apl) x.L.push_back(it);
for(info it : apr) x.R.push_back(it);
return x;
}
} val[N << 2];
void build(int l, int r, int x) {
if(l == r) return val[x] = {a[l], a[l], a[l], 1}, void();
int m = l + r >> 1;
build(l, m, x << 1), build(m + 1, r, x << 1 | 1);
val[x] = val[x << 1] + val[x << 1 | 1];
}
void modify(int l, int r, int x, int p) {
if(l == r) return val[x] = {a[l], a[l], a[l], 1}, void();
int m = l + r >> 1;
if(p <= m) modify(l, m, x << 1, p);
else modify(m + 1, r, x << 1 | 1, p);
val[x] = val[x << 1] + val[x << 1 | 1];
}
dat query(int l, int r, int ql, int qr, int x) {
if(ql <= l && r <= qr) return val[x];
int m = l + r >> 1;
if(qr <= m) return query(l, m, ql, qr, x << 1);
if(m < ql) return query(m + 1, r, ql, qr, x << 1 | 1);
return query(l, m, ql, qr, x << 1) + query(m + 1, r, ql, qr, x << 1 | 1);
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
build(1, n, 1);
cin >> q;
for(int i = 1; i <= q; i++) {
int op;
cin >> op;
if(op == 1) {
int x, y;
cin >> x >> y, a[x] = y;
modify(1, n, 1, x);
}
else {
int l, r;
cin >> l >> r;
dat res = query(1, n, l, r, 1);
cout << res.cnt << "\n";
}
}
return 0;
}