Codeforces Round #546 (Div. 2) E. Nastya Hasn't Written a Legend(线段树+思维(变量替换))

题目链接:

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] 。

 

思路:

设 t[i]=k[1]+k[2]+...+k[i-1] 。

设 b[i] = a[i]-t[i] 。

条件不等式 a[i+1]<a[i]+k[i]:只要满足这个条件,就把 a[i+1] 更新为 a[i]+k[i] 。

我们可以把条件不等式 a[i+1]<a[i]+k[i] 用 b[i] 来表示:

=> a[i+1]-k[i]<a[i]

=> a[i+1]-t[i]-k[i]<a[i]-t[i]

=> a[i+1]-t[i+1]<a[i]-t[i]

=> b[i+1]<b[i]

可以看到,只要满足 {\color{Red} b[i+1]<b[i]} ,就把 b[i+1] 更新为 b[i] 。

因为题目保证初始时:a[i+1]>=a[i]+k[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;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值