「Good Subarrays」Solution
简述题意
我们定义一个数组
b
b
b 是好的当且仅当所有的
b
i
≥
i
b_i \ge i
bi≥i。
现在给你
q
q
q 次操作,每次操作有两个数
p
p
p 和
x
x
x,求出把
a
p
a_p
ap 赋值成
x
x
x 后
a
a
a 数组好的子序列的个数,每次操作独立。
- 1 ≤ n , q ≤ 2 × 1 0 5 1 \le n,q \le 2 \times 10^5 1≤n,q≤2×105, 1 ≤ a i , p j , x j ≤ n 1 \le a_i,p_j,x_j \le n 1≤ai,pj,xj≤n。
思路
首先考虑 Easy Version \text{Easy Version} Easy Version 怎么做。
由于没有修改,求子序列的个数问题,一般都是从长度和端点入手。对于此题,考虑求出以 i i i 为开头的满足条件的子序列个数。
注意到,某个元素在子序列和原序列中的下标位置并不一样,所以很难处理。考虑令 c i = a i − i c_i = a_i-i ci=ai−i(算是一个 Trick \text{Trick} Trick 吧),那么,对于在原序列中的 [ l , r ] [l,r] [l,r],其满足条件就等价于:
∀ i ∈ [ l , r ] , c i + ( l − 1 ) ≥ 0 \forall i \in [l,r],c_i + (l - 1) \ge 0 ∀i∈[l,r],ci+(l−1)≥0
即
i ∈ [ l , r ] , m i n { c i } + ( l − 1 ) ≥ 0 i \in [l,r],\mathrm{min\{c_i\}}+(l-1) \ge 0 i∈[l,r],min{ci}+(l−1)≥0
由于最小值满足单调性,所以当左端点固定时,右端点一定单调,二分求解即可,下令 a n s ans ans 表示未修改时 a a a 中满足条件的子序列个数。
再考虑修改,对于每个 i i i,我们都可以求出一个 r i , R i r_i,R_i ri,Ri, r i r_i ri 表示对于 ∀ r ∈ [ i , r i ] \forall r \in [i , r_i] ∀r∈[i,ri] 都有 [ i , r ] [i,r] [i,r] 是一个合法子序列, R i R_i Ri 表示强制钦定 r i r_i ri 满足条件,即 a r i = i n f a_{r_i} = inf ari=inf 后最靠右的满足 [ i , R i ] [i,R_i] [i,Ri] 是一个合法子序列的点。
由于修改独立,且注意到修改一个点,只会对左边的点产生影响。
所以,对于每次修改 p , x p,x p,x,分成以下三种情况考虑:
- a p = x a_p=x ap=x,直接输出 a n s ans ans 即可。
- a p < x a_p<x ap<x,那么显然原来一些不满足条件的子序列现在可能满足条件了。对于某个 i i i,如果原来 r i = p − 1 r_i=p-1 ri=p−1,那么就证明 i i i 原来扩展右端点时在 p p p 这个点 “卡住了”,由于这样的 i i i 是连续的( r i ≥ r i − 1 r_i \ge r_{i-1} ri≥ri−1),不妨二分求出 [ l , r ] [l,r] [l,r] 表示对于 ∀ i ∈ [ l , r ] \forall i \in [l,r] ∀i∈[l,r] 都满足 r i = p − 1 r_i=p-1 ri=p−1。显然,只满足这个条件是不够的,还需满足 m i n { a i , a i + 1 , a i + 2 , . . . , a p − 1 , x − p } + ( i − 1 ) ≥ 0 \mathrm{min\{a_i,a_{i+1},a_{i+2},...,a_{p-1},x-p\}} + (i - 1) \ge 0 min{ai,ai+1,ai+2,...,ap−1,x−p}+(i−1)≥0 才行,然而这样的 i i i 又是连续的( i i i 越往左越难满足),所以不妨求出同时满足两个条件的 i i i 区间 [ l 2 , r 2 ] [l_2,r_2] [l2,r2],那么对于 i ∈ [ l 2 , r 2 ] i \in [l_2,r_2] i∈[l2,r2],其对答案的贡献不再是 r i − i + 1 r_i-i+1 ri−i+1,而是 R i − i + 1 R_i-i+1 Ri−i+1 了,这个通过前缀和预处理在原答案的基础上更新即可。
- a p > x a_p>x ap>x,同理可以二分求出 [ l , r ] ( r < i ) [l,r](r < i) [l,r](r<i), 满足 i ∈ [ l , r ] , r i ≥ p i \in[l,r],r_i\ge p i∈[l,r],ri≥p,然后在 [ l , r ] [l,r] [l,r] 的基础上,二分求出 [ l 2 , r 2 ] [l_2,r_2] [l2,r2], 满足 i ∈ [ l 2 , r 2 ] , m i n { a i , a i + 1 , a i + 2 , . . . . , a p − 1 , x − p } + ( i − 1 ) < 0 i \in [l_2,r_2],\mathrm{min\{a_i,a_{i+1},a_{i+2},....,a_{p-1},x-p\}}+(i-1) < 0 i∈[l2,r2],min{ai,ai+1,ai+2,....,ap−1,x−p}+(i−1)<0,那么对于 [ l 2 , r 2 ] [l_2,r_2] [l2,r2] 里面的点对答案的贡献不再是 r i − i + 1 r_i-i+1 ri−i+1,而是 ( p − 1 ) − i + 1 (p-1)-i+1 (p−1)−i+1,同理前缀和求出即可。
代码
码量并不大,但要注意一下二分的边界和一些 [ l , r ] [l,r] [l,r] 或 [ l 2 , r 2 ] [l_2,r_2] [l2,r2] 不存在的特殊情况。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 2e5 + 5;
int n , q , a[MAXN] , R[MAXN] , RR[MAXN] , Min[MAXN][20];
int idl[MAXN] , idr[MAXN] , pre1[MAXN] , pre2[MAXN];
int GetMin(int l , int r) {
int s = log2(r - l + 1);
return min(Min[l][s] , Min[r - (1 << s) + 1][s]);
}
int w(int l , int r) {return (l + r) * (r - l + 1) / 2;}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr) , cout.tie(nullptr);
cin >> n;
for (int i = 1 ; i <= n ; i ++) cin >> a[i] , Min[i][0] = (a[i] -= i);
for (int j = 1 ; j <= 18 ; j ++) {
for (int i = 1 ; i + (1 << j) - 1 <= n ; i ++) {
Min[i][j] = min(Min[i][j - 1] , Min[i + (1 << j - 1)][j - 1]);
}
}
int lst = 0;
for (int i = 1 ; i <= n ; i ++) {
int l = i , r = n;
while(l < r) {
int mid = l + r + 1 >> 1;
if (GetMin(i , mid) + (i - 1) >= 0) l = mid;
else r = mid - 1;
}
if (!idl[l]) idl[l] = i;
idr[l] = i;
R[i] = l , pre1[i] = pre1[i - 1] + (l - i + 1);
l = l + 1 , r = n;
if (l >= n) {
RR[i] = n , pre2[i] = pre2[i - 1] + (n - i + 1);
continue;
}
int ql = l + 1;
while(l < r) {
int mid = l + r + 1 >> 1;
if (GetMin(ql , mid) + (i - 1) >= 0) l = mid;
else r = mid - 1;
}
pre2[i] = pre2[i - 1] + (l - i + 1) , RR[i] = l;
}
cin >> q;
while(q --) {
int p , x;
cin >> p >> x;
x -= p;
if (p == 1 || a[p] == x) {
cout << pre1[n] << '\n';
continue;
}
if (a[p] < x) {
int l = idl[p - 1] , r = idr[p - 1];
while(l < r) {
int mid = l + r >> 1;
if (min(GetMin(mid , p - 1) , x) + (mid - 1) >= 0) r = mid;
else l = mid + 1;
}
if (min(GetMin(l , p - 1) , x) + (l - 1) < 0) cout << pre1[n] << '\n';
else cout << pre1[n] + (pre2[idr[p - 1]] - pre2[l - 1]) - (pre1[idr[p - 1]] - pre1[l - 1]) << '\n';
} else {
int l = 1 , r = p - 1;
while(l < r) {
int mid = l + r >> 1;
if (R[mid] >= p) r = mid;
else l = mid + 1;
}
if (R[l] < p) cout << pre1[n] << '\n';
else {
int l2 = l , r2 = p - 1;
while(l2 < r2) {
int mid = l2 + r2 + 1 >> 1;
if (min(GetMin(mid , p - 1) , x) + (mid - 1) < 0) l2 = mid;
else r2 = mid - 1;
}
if (min(GetMin(l2 , p - 1) , x) + (l2 - 1) >= 0) cout << pre1[n] << '\n';
else cout << pre1[n] - (pre1[l2] - pre1[l - 1]) + w(p - l2 , p - l) << '\n';
}
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】