Codeforces 1136E Nastya Hasn't Written a Legend (线段树教做人系列)

题意:有一个数组a和一个数组k,数组a一直保持一个性质:a[i + 1] >= a[i] + k[i]。有两种操作:1,给某个元素加上x,但是加上之后要保持数组a的性质。比如a[i]加上x之后,a[i + 1]<a[i] + k[i],那么a[i + 1]就变成a[i] + k[i],否则不变。同理,若a[i + 2]小于了现在的a[i + 1] + k[i + 1],那么a[i + 2]也变成a[i + 1] + k[i + 1],一直保持这个性质。第二章操作,询问数组a的区间[l, r]的区间和。

思路:容易发现,假设更改了x位置之后,恰好到位置y不需要更改元素,那么及其之后的位置肯定就不用更改了。所以,在查找这个位置的时候,我们可以二分查找。找到之后,对区间[x, y]进行操作。我们可以发现,区间[x, y]的数有规律。假设x位置的数是a[x],那么a[x + 1]是a[x] + k[x], a[x +  2]是a[x] + k[x + 1] + k[x + 2],以此类推。这个区间的和可以分为2部分:[y - x + 1]个a[x],和关于k的部分。可以发现,k[x]出现了(r - l + 1)次,k[x + 1]出现了(r - l)次,以此类推。那么怎么O(1)获得这个东西呢?我们先预处理k数组的前缀和(假设前缀和数组是b),那么我们再求b的前缀和(假设是数组c),那么c[i]就是出现了i次k[1],i - 1次k[2],以此类推。那么区间[x, y]中关于k的和就可以得出了:c[y] - c[x - 1] - [y - x + 1] * b[x]。前面c[y] - c[x - 1]可以O(1)算出,而后面的部分比较麻烦。怎么维护后面[y - x + 1] * b[x]这个部分呢?我们发现a[x]正好出现[y - x + 1]次,这样我们可以把a[x] - b[x]用一个懒标记维护,下放标记的时候区间的和为c[y] - c[x - 1] + (r - l + 1) * val (val是a[x] - b[x])。

代码:

#include <bits/stdc++.h>
#define ls(x) (x << 1)
#define rs(x) ((x << 1) | 1)
#define LL long long
using namespace std;
const int maxn = 100010;
const LL INF = 1e18;
LL a[maxn], b[maxn], c[maxn];
int n, m, now;
struct SegementTree{
	LL sum, tot;
	int l, r;
};
SegementTree tr[maxn * 4];

LL cal(int l, int r) {
	return (c[r] - c[l]) - (r - l) * (b[l]);
}
void pushup(int x) {
	tr[x].sum = tr[ls(x)].sum + tr[rs(x)].sum;
}

void maintain(int x,LL tot) {
	if(tot == -INF) return;
	int l = tr[x].l, r = tr[x].r;
	tr[x].sum = (r - l + 1) * tot + (c[r] - c[l - 1]);
	tr[x].tot = tot;
}

void pushdown(int x) {
	maintain(ls(x), tr[x].tot);
	maintain(rs(x), tr[x].tot);
	tr[x].tot = -INF;
}

void build(int x, int l, int r) {
	tr[x].l = l, tr[x].r = r;
	tr[x].tot = -INF;
	if(l == r) {
		tr[x].sum = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(ls(x), l, mid);
	build(rs(x), mid + 1, r);
	pushup(x);
}

LL query(int x, int l, int r, int ql, int qr) {
	if(l >= ql && r <= qr) {
		return tr[x].sum;
	}
	int mid = (l + r) >> 1;
	LL ans = 0;
	pushdown(x);
	if(ql <= mid) ans += query(ls(x), l, mid, ql, qr);
	if(qr > mid) ans += query(rs(x), mid + 1, r, ql, qr);
	return ans;
}

void update(int x, int l, int r, int ql, int qr, LL val) {
	if(l >= ql && r <= qr) {
		maintain(x, val);
		return;
	}
	int mid = (l + r) >> 1;
	pushdown(x);
	if(mid >= ql) update(ls(x), l, mid, ql, qr, val);
	if(mid < qr) update(rs(x), mid + 1, r, ql, qr, val);
	pushup(x);
}

int main() {
	int x, y;
	char s[5];
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
	}
	for (int i = 2; i <= n; i++) {
		scanf("%lld", &b[i]);
		b[i] += b[i - 1];
	}
	for (int i = 2; i <= n; i++)
		c[i] = c[i - 1] + b[i];
	build(1, 1, n);
	scanf("%d", &m);
	while(m--) {
		scanf("%s%d%d", s + 1, &x, &y);
		if(s[1] == ‘+‘) {
			int l = x, r = n;
			LL tmp = query(1, 1, n, x, x);
			while(l < r) {
				int mid = (l + r + 1) >> 1;
				LL tmp1 = query(1 , 1, n, mid, mid);
				if(tmp + y + b[mid] - b[x] > tmp1)
					l = mid;
				else r = mid - 1;
			}
			now = x;
			update(1, 1, n, x, l, tmp + y - b[x]);
		} else {
			printf("%lld\n", query(1, 1, n, x, y));
		}
	}
}

  

Codeforces 1136E Nastya Hasn't Written a Legend (线段树教做人系列)

原文地址:https://www.cnblogs.com/pkgunboat/p/10527569.html

时间: 2024-08-29 05:14:57

Codeforces 1136E Nastya Hasn't Written a Legend (线段树教做人系列)的相关文章

Nastya Hasn&#39;t Written a Legend(Codeforces Round #546 (Div. 2)E+线段树)

题目链接 传送门 题面 题意 给你一个\(a\)数组和一个\(k\)数组,进行\(q\)次操作,操作分为两种: 将\(a_i\)增加\(x\),此时如果\(a_{i+1}<a_i+k_i\),那么就将\(a_{i+1}\)变成\(a_i+k_i\),如果\(a_{i+2}<a_i+k_i\),则将\(a_{i+2}\)变成\(a_{i+1}+k_{i+1}\),以此类推. 查询\(\sum\limits_{i=l}^{r}a_i\). 思路 我们首先存下\(k\)数组的前缀和\(sum1\),

[cf 1136] E. Nastya Hasn&#39;t Written a Legend

题意 给两个数组分别为\(a\)和\(k\),有若干次操作: 1.给\(a_x\)加上\(y\),并以此对\(a_{x + i}(i \ge 1)\)赋值为\(\max \{a_{x + i}, a_{x + i - 1} + k_{x + i - 1}\}\). 2.询问区间\([l, r]\)的\(a_i\)的和. 题解 自闭了啊. 考虑把原序列分成若干个块,对于每个块内必须满足,除最后一个位置的所有位置\(i\),\(a_i + k_i = a_i + 1\).刚开始可以看成有\(n\)个

CF1136E Nastya Hasn&#39;t Written a Legend(线段树)

还能说什么呢,简直太妙了. $$a_{i+1}<a_i+k_i$$ $$a_{i+1}-k_i-k_{i-1}-\cdots-k_1<a_i+k_i-k_i-k_{i-1}-\cdots-k_1$$ $$a_{i+1}-k_i-k_{i-1}-\cdots-k_1<a_i-k_{i-1}-\cdots-k_1$$ 令 $k$ 的前缀和为 $kpre$. $$a_{i+1}-kpre_i<a_i-kpre_{i-1}$$ 令 $b_i=a_i-kpre_{i-1}$. $$b_{i+

Codeforces 458C Elections 贿赂选票抢主席! 线段树

题目链接:点击打开链接 题意: 给定n张选票,每张选票有2个参数,第一个参数表示这张选票选的人 第二个参数表示如果让这张选票改为选0号 的花费 问:使得0号的选票是最高的(不能有和0号相同)的最小花费 枚举0号的最终选票 那么已知0号最终选票,则有些人选票比0号大的,那些票都要买下来. 如果买完了还是达不到 最终选票,就从所有剩下的选票里找前k小的. 用线段树求前k小的数的和,然后_(:зゝ∠)_就可以了 #include<iostream> #include<cstdio> #i

Codeforces Round #244 (Div. 2) B. Prison Transfer 线段树rmq

B. Prison Transfer Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/problemset/problem/427/B Description The prison of your city has n prisoners. As the prison can't accommodate all of them, the city mayor has decided to transfer c

Codeforces Round #603 (Div. 2) E. Editor(线段树)

链接: https://codeforces.com/contest/1263/problem/E 题意: The development of a text editor is a hard problem. You need to implement an extra module for brackets coloring in text. Your editor consists of a line with infinite length and cursor, which point

Codeforces Round #426 (Div. 2) D. The Bakery(线段树维护dp)

题目链接: Codeforces Round #426 (Div. 2) D. The Bakery 题意: 给你n个数,划分为k段,每段的价值为这一段不同的数的个数,问如何划分,使得价值最大. 题解: 考虑dp[i][j]表示划分为前j个数划分为i段的最大价值,那么这就是一个n*n*k的dp, 考虑转移方程dp[i][j]=max{dp[i][k]+val[k+1][j]},我们用线段树去维护这个max,线段树上每个节点维护的值是dp[i][k]+val[k+1][j],对于每加进来的一个数a

Codeforces Round #424 (Div. 2) E. Cards Sorting(线段树)

题目链接:Codeforces Round #424 (Div. 2) E. Cards Sorting 题意: 将n个数放进一个队列,每次检查队首,看看是不是队列中最小的数,如果是就扔掉,如果不是就放到队尾. 这样直到队列为空,为需要操作多少次. 题解: 考虑用两个指针模拟,最开始now指针指向第一个数,然后nxt指针指向下一个将要被删除的数. 然后我们要算出这里需要移动多少步,然后删掉这个数,一直重复操作,直到将全部的数删完. nxt指针可以用set来维护,now指针可以用并查集来维护. 计

Codeforces Round #271 (Div. 2) F.Ant colony(线段树 + 统计区间内某数的个数)

F. Ant colony Mole is hungry again. He found one ant colony, consisting of n ants, ordered in a row. Each ant i (1 ≤ i ≤ n) has a strength si. In order to make his dinner more interesting, Mole organizes a version of «Hunger Games» for the ants. He c