题目链接:
E. Nastya Hasn't Written a Legend
题意:
给定一个长度为 n 的数组 a[i] 和一个长度为 n-1 的数组 k[i]。有2种操作:
1. 给定i,x,使得a[i] += x 。若更新后 a[i]+k[i]>a[i+1],则 a[i+1]=a[i]+k[i];若此时 a[i+1] 被更新了,且 a[i+1]+k[i+1]>a[i+2],则a[i+2]=a[i+1]+k[i+1];以此类推,直到把满足条件的都更新完。(x>=0)
2. 给定l,r,求 a[l]+a[l+1]+...+a[r] 。
思路:
设 。
设 。
条件不等式 :只要满足这个条件,就把 a[i+1] 更新为 a[i]+k[i] 。
我们可以把条件不等式 用 b[i] 来表示:
=>
=>
=>
=>
可以看到,只要满足 ,就把 b[i+1] 更新为 b[i] 。
因为题目保证初始时:,即
,即 b[i] 有序。之后由于操作的性质,更新后 b[i] 也会一直处于有序(递增)的状态。
处理操作1时,我们只要找到第一个大于 b[i]+x 的值的位置(location),然后把区间 [i,location-1]的值都更新为 b[i]+x 即可。
因此,我们用线段树维护b[i]的 区间最大值 和 区间和 。
维护区间最大值是为了寻找第一个大于 b[i]+x 的值的位置。从根节点(1-n)开始,若其左儿子的区间最大值 >=b[i]+x ,就往左儿子上找,否则就在右儿子上找。注意细节:当 b[i]+x 比 当前最大值还大,location = n ;当 x = 0,location 可能会比 i 要小,其实此时不需要更新;最后就是查找时记得 pushdown 来下推 lazy 标记。
操作2直接用线段树查找区间 [l,r] 的区间和,然后因为实际要求的是 a[i] 的区间和,所以再加上 t[l]+t[l+1]+...+t[r] 即可。
Tips:有一个细节调了半天,就是 lazy 标记记得初始化为 inf (=1e18打个比方),不能默认初始为 0 ,因为存在把区间值都更新为0的情况。
Code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 1e5 + 50;
const ll inf = 1e18;
int n, q;
int location;
ll a[MAX], k[MAX];
ll t[MAX], b[MAX], Sum2[MAX];
ll sum[MAX << 2], Add[MAX << 2], mx[MAX << 2];
void pushup(int root) {
sum[root] = sum[root << 1] + sum[root << 1 | 1];
mx[root] = max(mx[root << 1], mx[root << 1 | 1]);
}
void pushdown(int root, int ln, int rn) {
if (Add[root] != inf) {
Add[root << 1] = Add[root];
Add[root << 1 | 1] = Add[root];
sum[root << 1] = 1ll * ln * Add[root];
sum[root << 1 | 1] = 1ll * rn * Add[root];
mx[root << 1] = Add[root];
mx[root << 1 | 1] = Add[root];
Add[root] = inf;
}
}
void build(int l, int r, int root) {
if (l == r) {
sum[root] = b[l];
mx[root] = b[l];
return;
}
int mid = (l + r) >> 1;
build(l, mid, root << 1);
build(mid + 1, r, root << 1 | 1);
pushup(root);
}
void update(int L, int R, ll val, int l, int r, int root) {
if (L <= l && r <= R) {
sum[root] = 1ll * (r - l + 1)*val;
mx[root] = val;
Add[root] = val;
return;
}
int mid = (l + r) >> 1;
pushdown(root, mid - l + 1, r - mid);
if (L <= mid)
update(L, R, val, l, mid, root << 1);
if (R > mid)
update(L, R, val, mid + 1, r, root << 1 | 1);
pushup(root);
}
ll query(int L, int R, int l, int r, int root) {
if (L <= l && r <= R) {
return sum[root];
}
int mid = (l + r) >> 1;
pushdown(root, mid - l + 1, r - mid);
ll ans = 0;
if (L <= mid) ans += query(L, R, l, mid, root << 1);
if (R > mid) ans += query(L, R, mid + 1, r, root << 1 | 1);
return ans;
}
//寻找第一个大于 b[i]+x 的值的位置
void search(int l, int r, int root, ll val) {
if (l == r) {
location = l;
return;
}
int mid = (l + r) >> 1;
//记得 pushdown 来下推 lazy 标记
pushdown(root, mid - l + 1, r - mid);
if (mx[root << 1] >= val) {
search(l, mid, root << 1, val);
}
else {
search(mid + 1, r, root << 1 | 1, val);
}
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
for (int i = 1; i < n; i++) {
scanf("%lld", &k[i]);
}
//注意lazy标记初始化
fill(Add, Add + (MAX << 2), inf);
t[0] = 0;
for (int i = 1; i <= n; i++)
t[i] = t[i - 1] + k[i - 1];
for (int i = 1; i <= n; i++)
b[i] = a[i] - t[i];
for (int i = 1; i <= n; i++)
Sum2[i] = Sum2[i - 1] + t[i];
build(1, n, 1);
scanf("%d", &q);
while (q--) {
char op[5];
scanf("%s", op);
if (op[0] == '+') {
ll pos, x;
scanf("%lld%lld", &pos, &x);
if (x == 0) continue;
ll now = query(pos, pos, 1, n, 1);
now += x;
if (now > mx[1]) {
location = n;
}
else {
search(1, n, 1, now);
location--;
}
update(pos, location, now, 1, n, 1);
}
else {
int l, r;
scanf("%d%d", &l, &r);
ll ans = query(l, r, 1, n, 1);
ans += (Sum2[r] - Sum2[l - 1]);
printf("%lld\n", ans);
}
}
return 0;
}