「Nastya Hasn‘t Written a Legend」Solution

简述题意

给定长为 n n n 的数组 a a a,长为 n − 1 n-1 n1 的数组 k k k。试支持以下 2 2 2 种操作:

  1. + i x,表示 a i ← a i + x a_i \gets a_i + x aiai+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+1ai+ki,ii+1,否则结束本次操作。

  2. s l r,输出 ∑ i = l r a i \sum\limits_{i = l}^r a_i i=lrai

  • 1 ≤ n , q ≤ 1 0 5 1 \le n,q \le 10^5 1n,q105
  • 1 ≤ i ≤ n , 1 ≤ l ≤ r ≤ n 1 \le i \le n,1 \le l \le r \le n 1in,1lrn
  • ∣ 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 k106,ai109,0x106

思路

考虑修改操作,由于一旦遇到某个 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 aiai+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+prekj1preki1>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]aipreki1>ajprekj1

考虑转化为最值。

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] aipreki1>max{ajprekj1}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} ajai+prekj1preki1。然而,我们很难直接维护 a i a_i ai 的值,因为有一个 p r e k j − 1 prek_{j-1} prekj1 很棘手。不妨观察上述式子,发现 a i − p r e k i − 1 a_i - prek_{i-1} aipreki1 这样的式子出现频率很高,所以试着维护 a i − p r e k i − 1 a_i - prek_{i-1} aipreki1 而非 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+prekj1preki1,而对于每个 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} ajprekj1=ai+preki1,这对于每个修改来说是个定值!直接线段树区间推平即可。巧合的是,如果我们再维护区间 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=lraipreki1,然后再预处理 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=lrpreki1,相加即可。

总结一下,线段树维护 a i − p r e k i − 1 a_i-prek_{i-1} aipreki1 这个序列。

  • 对于修改操作,不妨令线段树上点 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} preki1 要加回来,所以维护 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;
}
  • 21
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值