简述题意
给定长为 n n n 的数组 a a a,长为 n − 1 n-1 n−1 的数组 k k k。试支持以下 2 2 2 种操作:
-
+ i x
,表示 a i ← a i + x a_i \gets a_i + x ai←ai+x,然后重复以下过程:若 a i + 1 < a i + k i a_{i+1} < a_i + k_i ai+1<ai+ki,则 a i + 1 ← a i + k i , i ← i + 1 a_{i + 1} \gets a_i + k_i,i \gets i + 1 ai+1←ai+ki,i←i+1,否则结束本次操作。 -
s l r
,输出 ∑ i = l r a i \sum\limits_{i = l}^r a_i i=l∑rai。
- 1 ≤ n , q ≤ 1 0 5 1 \le n,q \le 10^5 1≤n,q≤105
- 1 ≤ i ≤ n , 1 ≤ l ≤ r ≤ n 1 \le i \le n,1 \le l \le r \le n 1≤i≤n,1≤l≤r≤n
- ∣ k ∣ ≤ 1 0 6 , ∣ a i ∣ ≤ 1 0 9 , 0 ≤ x ≤ 1 0 6 |k| \le 10^6,|a_i| \le 10^9, 0 \le x \le 10^6 ∣k∣≤106,∣ai∣≤109,0≤x≤106
思路
考虑修改操作,由于一旦遇到某个 i i i 不满足 a i + 1 < a i + k i a_{i+1} < a_i+k_i ai+1<ai+ki,操作就会立即中断。显然,如果我们修改 a i ← a i + x a_i \leftarrow a_i+x ai←ai+x,只会影响到 [ i , x ] ( x ∈ [ i , n ] ) [i,x](x \in [i , n]) [i,x](x∈[i,n]) 这样一个连续的区间。
考虑怎么求出
x
x
x,以下不妨令
p
r
e
k
prek
prek 为
k
k
k 数组的前缀和。
假设操作进行到
x
x
x 中断,那么我们需要保证:
∀ j ∈ [ i + 1 , x ] , a i + p r e k j − 1 − p r e k i − 1 > a j \forall j \in [i+1,x],a_i + prek_{j-1} - prek_{i-1} > a_j ∀j∈[i+1,x],ai+prekj−1−preki−1>aj
考虑将与 i , j i,j i,j 相关的式子分别移至等号两边。
∀ j ∈ [ i + 1 , x ] , a i − p r e k i − 1 > a j − p r e k j − 1 \forall j \in [i+1,x],a_i - prek_{i-1} > a_j - prek_{j-1} ∀j∈[i+1,x],ai−preki−1>aj−prekj−1
考虑转化为最值。
a i − p r e k i − 1 > max { a j − p r e k j − 1 } , j ∈ [ i + 1 , x ] a_i - prek_{i-1} >\max\{a_j - prek_{j-1}\},j \in [i + 1 , x] ai−preki−1>max{aj−prekj−1},j∈[i+1,x]
不妨假设我们已求出 i i i 影响的区间 [ i , x ] [i,x] [i,x]。那么,对于每一个 j ∈ [ i , x ] j \in [i,x] j∈[i,x], a j ← a i + p r e k j − 1 − p r e k i − 1 a_j \leftarrow a_i + prek_{j-1} - prek_{i-1} aj←ai+prekj−1−preki−1。然而,我们很难直接维护 a i a_i ai 的值,因为有一个 p r e k j − 1 prek_{j-1} prekj−1 很棘手。不妨观察上述式子,发现 a i − p r e k i − 1 a_i - prek_{i-1} ai−preki−1 这样的式子出现频率很高,所以试着维护 a i − p r e k i − 1 a_i - prek_{i-1} ai−preki−1 而非 a i a_i ai。
我们发现,一次修改后,某个连续区间 [ i , x ] [i,x] [i,x] 中的元素都被推平成了 a i + p r e k j − 1 − p r e k i − 1 a_i + prek_{j-1} - prek_{i-1} ai+prekj−1−preki−1,而对于每个 j j j,其 a j − p r e k j − 1 = a i + p r e k i − 1 a_j-prek_{j-1}=a_i+prek_{i-1} aj−prekj−1=ai+preki−1,这对于每个修改来说是个定值!直接线段树区间推平即可。巧合的是,如果我们再维护区间 m a x max max,由于 m a x max max 有单调性,结合二分也顺便求出了 x x x。
考虑查询操作,线段树维护区间和可以求出 ∑ i = l r a i − p r e k i − 1 \sum\limits_{i = l}^r a_i-prek_{i-1} i=l∑rai−preki−1,然后再预处理 p r e k prek prek 数组的前缀 P r e Pre Pre,便可以求出 ∑ i = l r p r e k i − 1 \sum\limits_{i = l}^r prek_{i-1} i=l∑rpreki−1,相加即可。
总结一下,线段树维护 a i − p r e k i − 1 a_i-prek_{i-1} ai−preki−1 这个序列。
- 对于修改操作,不妨令线段树上点 i i i 的值为 v a l val val,那么将区间 [ i , x ] [i , x] [i,x] 推平为 v a l + x val+x val+x。
- 对于查询操作,每个元素减去的 p r e k i − 1 prek_{i-1} preki−1 要加回来,所以维护 p r e k prek prek 的前缀和即可。
代码
注意线段树的
l
a
z
y
lazy
lazy 标初始不能置为
−
1
-1
−1!因为
−
1
-1
−1 在此题中有实际意义!
还有就是二分的边界!!!
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 2e5 + 5;
int n , q , a[MAXN] , k[MAXN] , prek[MAXN] , Pre[MAXN];
namespace Segment{
struct tree{
int l , r , sum , Max , tag;
}tree[MAXN << 3];
void pushup(int p) {
tree[p].sum = tree[p << 1].sum + tree[p << 1 | 1].sum;
tree[p].Max = max(tree[p << 1].Max , tree[p << 1 | 1].Max);
}
void pushdown(int p) {
if (tree[p].tag != -0x3f3f3f3f) {
tree[p << 1].tag = tree[p << 1 | 1].tag = tree[p << 1].Max = tree[p << 1 | 1].Max = tree[p].tag;
tree[p << 1].sum = (tree[p << 1].r - tree[p << 1].l + 1) * tree[p].tag;
tree[p << 1 | 1].sum = (tree[p << 1 | 1].r - tree[p << 1 | 1].l + 1) * tree[p].tag;
tree[p].tag = -0x3f3f3f3f;
}
}
void build(int p , int l , int r) {
tree[p].tag = -0x3f3f3f3f , tree[p].l = l , tree[p].r = r;
if (l == r) {
tree[p].Max = tree[p].sum = a[l] - prek[l - 1];
return;
}
int mid = l + r >> 1;
build(p << 1 , l , mid) , build(p << 1 | 1 , mid + 1 , r);
pushup(p);
}
void update(int p , int l , int r , int v) {
if (l > r) return;
if (tree[p].l >= l && tree[p].r <= r) {
tree[p].sum = (tree[p].r - tree[p].l + 1) * v;
tree[p].tag = tree[p].Max = v;
return;
}
pushdown(p);
int mid = tree[p].l + tree[p].r >> 1;
if (l <= mid) update(p << 1 , l , r , v);
if (r > mid) update(p << 1 | 1 , l , r , v);
pushup(p);
}
int QueryMax(int p , int l , int r) {
if (tree[p].l >= l && tree[p].r <= r) return tree[p].Max;
pushdown(p);
int mid = tree[p].l + tree[p].r >> 1 , Max = -1e9;
if (l <= mid) Max = max(Max , QueryMax(p << 1 , l , r));
if (r > mid) Max = max(Max , QueryMax(p << 1 | 1 , l , r));
return Max;
}
int QuerySum(int p , int l , int r) {
if (tree[p].l >= l && tree[p].r <= r) return tree[p].sum;
pushdown(p);
int mid = tree[p].l + tree[p].r >> 1 , sum = 0;
if (l <= mid) sum += QuerySum(p << 1 , l , r);
if (r > mid) sum += QuerySum(p << 1 | 1 , l , r);
return sum;
}
}
using namespace Segment;
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];
for (int i = 1 ; i < n ; i ++) cin >> k[i] , prek[i] = prek[i - 1] + k[i];
for (int i = 1 ; i <= n ; i ++) Pre[i] = Pre[i - 1] + prek[i];
build(1 , 1 , n);
cin >> q;
while(q --) {
char type;
int x , y;
cin >> type >> x >> y;
if (type == '+') {
a[x] += y;
int val = QuerySum(1 , x , x) + y;
int l = x + 1 , r = n;
while(l < r) {
int mid = l + r + 1 >> 1;
if (QueryMax(1 , x + 1 , mid) < val) l = mid;
else r = mid - 1;
}
if (x == n || QueryMax(1 , x + 1 , l) >= val) l = x;
update(1 , x , l , val);
} else {
cout << QuerySum(1 , x , y) + Pre[y - 1] - Pre[(x - 2 < 1 ? 0 : x - 2)] << '\n';
}
}
return 0;
}