『差分·线段树』Nastya Hasn't Written a Legend

题目描述

在这里插入图片描述

题解

不知道如何想到差分…反正正解很玄学…

如果对于i来说需要满足赋值条件,满足条件 a i + 1 − a i &lt; k i a_{i+1}-a_i&lt;k_i ai+1ai<ki

如果令 c i = ∑ j = 1 i k i c_i=\sum_{j=1}^{i} k_i ci=j=1iki,就有 a i + 1 − a i &lt; c i − c i − 1 a_{i+1}-a_i&lt;c_i-c_{i-1} ai+1ai<cici1,然后就有 a i + 1 − c i &lt; a i − c i − 1 a_{i+1}-c{i}&lt;a_i-c_{i-1} ai+1ci<aici1

再另 b i = a i − c i − 1 b_i=a_i-c_{i-1} bi=aici1,则有 b i + 1 &lt; b i b_{i+1}&lt;b{i} bi+1<bi.因此我们只需要用维护单调不下降的b就行。

具体如何实现呢?

我们需要快速找到连续的一段区间并进行修改,可以使用线段数来维护;并且找到第一个符合条件的数字,将这个点左边的所有点修改即可;即找到第一个大于或等于 b i b_i bi的数,然后全部修改成 b i b_i bi即可。

至于线段数如何找到最大值,维护区间最大值即可。利用分治的思想就可以做到。

#include <bits/stdc++.h>

#define int long long

using namespace std;
const int N = 200000;

int n, Q;
int a[N], k[N], c[N], S[N], b[N];

struct node {
	int max, sum, tag, l, r;
} t[N*10];

void build(int p,int l,int r)
{
	t[p].l = l, t[p].r = r, t[p].tag = -233;
	if (l == r) {
		t[p].max = t[p].sum = b[l];
		return;
	}
	int mid = l+r >> 1;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
	t[p].max = max(t[p*2].max,t[p*2+1].max);
	t[p].sum = t[p*2].sum+t[p*2+1].sum;
	return;
}

void spread(int p)
{
	if (t[p].tag == -233) return;
	t[p*2].tag = t[p*2+1].tag = t[p].tag;
	t[p*2].max = t[p*2+1].max = t[p].tag;
	t[p*2].sum = (t[p*2].r-t[p*2].l+1)*t[p].tag;
	t[p*2+1].sum = (t[p*2+1].r-t[p*2+1].l+1)*t[p].tag;
	t[p].tag = -233;
	return;
}

int ask(int p,int l,int r)
{
	if (l <= t[p].l && r >= t[p].r) return t[p].sum;
	int mid = t[p].l + t[p].r >> 1, sum = 0;
	spread(p);
	if (l <= mid) sum += ask(p*2,l,r);
	if (r > mid) sum += ask(p*2+1,l,r);
	return sum;
}

int find(int p,int l,int r,int v)
{
	if (t[p].l == t[p].r) return t[p].l;
	int mid = t[p].l+t[p].r >> 1;
	spread(p);
	if (l <= mid && t[p*2].max >= v) return find(p*2,l,r,v);
	else if (r > mid && t[p*2+1].max >= v) return find(p*2+1,l,r,v);
	else return n+1; 
}

void change(int p,int l,int r,int v)
{
	if (l <= t[p].l && r >= t[p].r) {
		t[p].tag = t[p].max = v;
		t[p].sum = (t[p].r-t[p].l+1) * v;
		return;
	} 
	spread(p);
	int mid = t[p].l + t[p].r >> 1;
	if (l <= mid) change(p*2,l,r,v);
	if (r > mid) change(p*2+1,l,r,v);
	t[p].max = max(t[p*2].max,t[p*2+1].max);
	t[p].sum = t[p*2].sum + t[p*2+1].sum;
	return;
}

int sum(int l,int r)
{
	return ask(1,l,r) + S[r-1] - S[l-2];
}
//求解原序列区间和 

void add(int x,int v)
{
	int val = ask(1,x,x) + v;//得到bi修改后的值 
	int t = find(1,x+1,n,val)-1;//找到第一个大于等于bi的下表 
	change(1,x,t,val);//将找到的范围全部改成修改后的值 
	return;
}

signed main(void)
{
	scanf("%lld", &n);
	for (int i=1;i<=n;++i) scanf("%lld", a+i);
	for (int i=1;i<n;++i) scanf("%lld", k+i);
	for (int i=1;i<=n;++i) c[i] = c[i-1]+k[i];
	for (int i=1;i<=n;++i) b[i] = a[i]-c[i-1];
	for (int i=1;i<=n;++i) S[i] = c[i]+S[i-1];
	build(1,1,n); 
	scanf("%lld", &Q);
	while (Q --) {
		char c = getchar();int x, y;
		while (c ^ 's' && c ^ '+') c = getchar();
		scanf("%lld %lld", &x, &y);
		if (c == 's') printf("%lld\n", sum(x,y));
		else add(x,y);
	}
	return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值