线段树,ACM暑期培训

                        例题:245. 你能回答这些问题吗

245. 你能回答这些问题吗 - AcWing题库

给定长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:

  1. 1 x y,查询区间 [x,y] 中的最大连续子段和,即 maxx≤l≤r≤ymax�≤�≤�≤�{∑i=lrA[i]∑�=���[�]}。
  2. 2 x y,把 A[x]改成 y。

对于每个查询指令,输出一个整数表示答案。

输入格式

第一行两个整数 N,M。

第二行 N 个整数 A[i]。

接下来 M 行每行 33 个整数 k,x,y,k=1 表示查询(此时如果 x>y,请交换 x,y),k=2 表示修改。

输出格式

对于每个查询指令输出一个整数表示答案。

每个答案占一行。

数据范围

N≤500000,M≤100000
−1000≤A[i]≤1000

输入样例:
5 3
1 2 -3 4 5
1 2 3
2 2 -1
1 3 2
输出样例:
2
-1

 解析:单点修改,区间查询

(树状数组也能单点修改,区间查询,但无法查最大值,树状数组相当于升级版前缀和)

由于左右子节点互相独立,因此向父节点转移的 tmax
 无非是以下三种情况:

1.左子节点的最大连续子段和 l.dat
2.右子节点的最大连续子段和 r.dat
3.左子节点的最大后缀和 {l.rmax} + 右子节点的最大前缀和 {r.lmax}。

t[p].dat = max(max(t[l].dat, t[r].dat), t[l].rmax + t[r].lmax);
	t[p].rmax = max(t[r].rmax, t[l].rmax + t[r].sum);
	t[p].lmax = max(t[l].lmax, t[l].sum + t[r].lmax);
	t[p].sum = t[l].sum + t[r].sum;

所以代码为:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 5e5 + 5;
struct node {
	int l, r;
	int dat, sum, lmax, rmax;
}t[N * 4];
int arr[N];
int n, m;

void build(int p, int l, int r) {
	t[p].l = l, t[p].r = r;
	if (l == r) {
		t[p].dat = arr[l];
		t[p].sum = arr[l];
		t[p].lmax = arr[l];
		t[p].rmax = arr[l];
		return;
	}
	int mid = (l + r) / 2;
	build(p * 2, l, mid);
	build(p * 2 + 1, mid + 1, r);
	l = p * 2, r = p * 2 + 1;
	t[p].dat = max(max(t[l].dat, t[r].dat), t[l].rmax + t[r].lmax);
	t[p].rmax = max(t[r].rmax, t[l].rmax + t[r].sum);
	t[p].lmax = max(t[l].lmax, t[l].sum + t[r].lmax);
	t[p].sum = t[l].sum + t[r].sum;
}

void fun(int p) {
	int l = p * 2, r = p * 2 + 1;
	t[p].dat = max(max(t[l].dat, t[r].dat), t[l].rmax + t[r].lmax);
	t[p].rmax = max(t[r].rmax, t[l].rmax + t[r].sum);
	t[p].lmax = max(t[l].lmax, t[l].sum + t[r].lmax);
	t[p].sum = t[l].sum + t[r].sum;
}

void change(int p, int x, int v) {
	if (t[p].l == t[p].r) {
		t[p].dat = v;
		t[p].sum = v;
		t[p].lmax = v;
		t[p].rmax = v;
		return;
	}
	int mid = (t[p].l + t[p].r) / 2;
	if (x <= mid) {
		change(p * 2, x, v);
	}
	else {
		change(p * 2 + 1, x, v);
	}
	fun(p);
}
void fun2(node& ret, node& l, node& r) {
	ret.dat = max(max(l.dat, r.dat), l.rmax + r.lmax);
	ret.rmax = max(r.rmax, l.rmax + r.sum);
	ret.lmax = max(l.lmax, l.sum + r.lmax);
	r.sum = l.sum + r.sum;
}

node ask(int p, int l, int r) {
	if (l <= t[p].l && r >= t[p].r)return t[p];
	int mid = (t[p].l + t[p].r) / 2;
	if (r <= mid)return ask(p * 2, l, r);
	else if (l > mid)return ask(p * 2 + 1, l, r);
	else {
		node left = ask(p * 2, l, r);
		node right = ask(p * 2 + 1, l, r);
		node ret;
		fun2(ret, left, right);
		return ret;
	}
}

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		scanf("%d", &arr[i]);
	}
	build(1, 1, n);
	int temp, x, y;
	while (m--) {
		scanf("%d%d%d", &temp, &x, &y);
		if (temp == 1) {
			if (x > y) swap(x, y);
			cout << ask(1, x, y).dat << endl;
		}
		else if (temp == 2) {
			change(1, x, y);
		}
	}
	return 0;
}

                                   区间和-线段树

Contest (nefu.edu.cn)

Problem:B
Time Limit:1000ms
Memory Limit:65535K

Description

给定一数列,规定有两种操作,一是修改某个元素,二是求区间的连续和。

Input

输入数据第一行包含两个正整数n,m(n<=100000,m<=500000),以下是m行,
    每行有三个正整数k,a,b(k=0或1, a,b<=n).k=0时表示将a处数字加上b,k=1时表示询问区间[a,b]内所有数的和。

Output

 对于每个询问输出对应的答案。

Sample Input

10 20
    0 1 10
    1 1 4
    0 6 6
    1 4 10
    1 8 9
    1 4 9
    0 10 2
    1 1 8
    0 2 10
    1 3 9
    0 7 8
    0 3 10
    0 1 1
    1 3 8
    1 6 9
    0 5 5
    1 1 8
    0 4 2
    1 2 8
    0 1 1

Sample Output

10
    6
    0
    6
    16
    6
    24
    14
    50
    41

Hint

 

Source

信息学奥赛一本通

 解析:单点修改,区间查询

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
struct node {
	int l, r;
	LL dat;
}t[N * 4];
int arr[N];
int n, m;

void build(int p, int l, int r) {
	t[p].l = l, t[p].r = r;
	if (l == r) {
		t[p].dat = arr[l];
		return;
	}
	int mid = (l + r) / 2;
	build(p * 2, l, mid);
	build(p * 2 + 1, mid + 1, r);
	l = p * 2, r = p * 2 + 1;
	t[p].dat = t[l].dat + t[r].dat;
}

void change(int p, int x, int v) {
	if (t[p].l == t[p].r) {
		t[p].dat += v;
		return;
	}
	int mid = (t[p].l + t[p].r) / 2;
	if (x <= mid) {
		change(p * 2, x, v);
	}
	else {
		change(p * 2 + 1, x, v);
	}
	int l = p * 2, r = p * 2 + 1;
	t[p].dat = t[l].dat + t[r].dat;
}
void fun2(node& ret, node& l, node& r) {
	ret.dat = l.dat + r.dat;
}

node ask(int p, int l, int r) {
	if (l <= t[p].l && r >= t[p].r)return t[p];
	int mid = (t[p].l + t[p].r) / 2;
	if (r <= mid)return ask(p * 2, l, r);
	else if (l > mid)return ask(p * 2 + 1, l, r);
	else {
		node left = ask(p * 2, l, r);
		node right = ask(p * 2 + 1, l, r);
		node ret;
		fun2(ret, left, right);
		return ret;
	}
}

int main() {
	cin >> n >> m;
	
	build(1, 1, n);
	int temp, x, y;
	while (m--) {
		scanf("%d%d%d", &temp, &x, &y);
		if (temp == 1) {
			if (x > y) swap(x, y);
			printf("%lld\n",ask(1,x,y).dat);
		}
		else  {
			change(1, x, y);
		}
	}
	return 0;
}

                                快乐的雨季---线段树

Contest (nefu.edu.cn)

Problem:A
Time Limit:5000ms
Memory Limit:65535K

Description

六月到来,长江流域进入了雨季,在长江流域有一个小镇,这个小镇上的百姓都住在一条直线上,共有n户人家,编号为1~n,在直线上按编号依次坐落。进入雨季来,这个小镇共下了q次雨,每次下雨覆盖范围是一个连续的区间(L,R),表示编号为L至R的家庭位于降雨区,降雨量为x。镇长非常关心雨后受灾问题,于是每场雨后他想知道该场雨降雨区的家庭自进入雨季以来总的降水量,你能帮帮他吗?

Input

多组数据输入。
每组数据的第一行n,q(1<=n,q<=1e5)表示该小镇共有n户人家,共下了q场雨。
接下来q行,每行3个整数L,R,x (1<=L<=R<=n,1<=x<=10000)表示该场雨覆盖范围和降雨量。

Output

每场雨后输出降雨区总的降水量(自进入雨季来总的降水量,包括该场雨)。

Sample Input

6 4
2 5 10
3 6 7
1 6 102
4 5 57

Sample Output

40
58
680
352

Hint

Source

CJJ

解析:区间修改,区间查询,懒标记

详见《算法竞赛进阶指南》238;

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
struct Tree {
	int l, r;
	LL sum, add;
}tree[4*N];
int arr[N], n, m;

void build(int p, int l, int r) {
	tree[p].l = l, tree[p].r = r;
	if (l == r) {
		tree[p].sum = arr[l];
		return;
	}
	int mid = (l + r) / 2;
	build(p * 2, l, mid);
	build(p * 2 + 1, mid + 1, r);
	tree[p].sum = tree[2 * p].sum + tree[2 * p + 1].sum;
}

void spread(int p) {
	if (tree[p].add) {
		tree[2 * p].sum += tree[p].add * (tree[2 * p].r - tree[2 * p].l + 1);
		tree[2 * p+1].sum += tree[p].add * (tree[2 * p+1].r - tree[2 * p+1].l + 1);
		tree[2 * p].add += tree[p].add;
		tree[2 * p+1].add += tree[p].add;
		tree[p].add = 0;
	}
}

void change(int p, int l, int r, int d) {
	if (l <= tree[p].l && r >= tree[p].r) {
		tree[p].sum += (long long)d * (tree[p].r - tree[p].l + 1);
		tree[p].add += d;
		return;
	}
	spread(p);
	int mid = (tree[p].l + tree[p].r) / 2;
	if (l <= mid)change(2 * p, l, r, d);
	if (r > mid)change(2 * p + 1, l, r, d);
	tree[p].sum = tree[2 * p].sum + tree[p * 2 + 1].sum;
}

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

int main() {
	/*cin >> n >> m;*/
	/*for (int i = 1; i <= n; i++) {
		scanf("%d", arr[i]);
	}*/
	while (scanf("%d%d",&n,&m)!=EOF) {
		memset(arr, 0, sizeof(arr));
		memset(tree, 0, sizeof(tree));
		build(1, 1, n);
		while (m--) {
			int l, r, d;
			scanf("%d%d%d", &l, &r, &d);
			change(1, l, r, d);
			printf("%lld\n", ask(1, l, r));
		}
	}
	return 0;
}

                A Simple Problem with Integers-线段树

Problem:C
Time Limit:1000ms
Memory Limit:65535K

Description

You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.
一个长度为n的序列,m次询问。区间所有数加上一个值,或者询问一段区间的和。n,m<=1e5

Input

he first line contains two numbers N and M. 1 ≤ N,M ≤ 100000.
The second line contains N numbers, the initial values of A1, A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000.
Each of the next M lines represents an operation.
"C a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000.
"Q a b" means querying the sum of Aa, Aa+1, ... , Ab.

Output

输出查询结果。

Sample Input

10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4

Sample Output

4
55
9
15

Hint

区间加上一个值,int  能搞定吗?

Source

POJ Monthly--2007.11.25, Yang Yi

解析:区间修改,区间查询,懒标记

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
struct Tree {
	int l, r;
	LL sum, add;
}tree[4 * N];
int arr[N], n, m;

void build(int p, int l, int r) {
	tree[p].l = l, tree[p].r = r;
	if (l == r) {
		tree[p].sum = arr[l];
		return;
	}
	int mid = (l + r) / 2;
	build(p * 2, l, mid);
	build(p * 2 + 1, mid + 1, r);
	tree[p].sum = tree[2 * p].sum + tree[2 * p + 1].sum;
}

void spread(int p) {
	if (tree[p].add) {
		tree[2 * p].sum += tree[p].add * (tree[2 * p].r - tree[2 * p].l + 1);
		tree[2 * p + 1].sum += tree[p].add * (tree[2 * p + 1].r - tree[2 * p + 1].l + 1);
		tree[2 * p].add += tree[p].add;
		tree[2 * p + 1].add += tree[p].add;
		tree[p].add = 0;
	}
}

void change(int p, int l, int r, int d) {
	if (l <= tree[p].l && r >= tree[p].r) {
		tree[p].sum += (long long)d * (tree[p].r - tree[p].l + 1);
		tree[p].add += d;
		return;
	}
	spread(p);
	int mid = (tree[p].l + tree[p].r) / 2;
	if (l <= mid)change(2 * p, l, r, d);
	if (r > mid)change(2 * p + 1, l, r, d);
	tree[p].sum = tree[2 * p].sum + tree[p * 2 + 1].sum;
}

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

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		scanf("%d", &arr[i]);
	}
	build(1, 1, n);
	while (m--) {
		char op[2];
		int l, r, d;
		scanf("%s%d%d", &op, &l, &r);
		if (op[0] == 'C') {
			scanf("%d", &d);
			change(1, l, r, d);
		}
		else
		printf("%lld\n", ask(1, l, r));
	}
	return 0;
}

                                校门外的树3-线段树

Problem:F
Time Limit:1000ms
Memory Limit:65535K

Description

校门外有很多树,有苹果树,香蕉树,有会扔石头的,有可以吃掉补充体力的……
如今学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两个操作:
K=1,K=1,读入l、r表示在区间[l,r]中种上一种树,每次操作种的树的种类都不同
K=2,读入l,r表示询问l~r之间能见到多少种树
(l,r>0)

Input

第一行n,m表示道路总长为n,共有m个操作
接下来m行为m个操作

Output

对于每个k=2输出一个答案

Sample Input

5 4
1 1 3
2 2 5
1 2 4
2 3 5

Sample Output

1
2

Hint

范围:20%的数据保证,n,m<=100
60%的数据保证,n <=1000,m<=50000
100%的数据保证,n,m<=50000

Source

vijos  dejiyu@CSC WorkGroup

解析:不会

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int n, m;
LL c[N], c1[N];

void add(int x, int d, LL* arr) {
	for (; x <= n; x += x & -x) {
		arr[x] += d;
	}
}

LL sum(int x, LL* arr) {
	LL ans = 0;
	for (; x; x -= x & -x) {
		ans += arr[x];
	}return ans;
}

int main() {
	cin >> n >> m;
	int op,l, r;
	int mx = 0;
	while (m--) {
		scanf("%d%d%d", &op, &l, &r);
		if (op == 1) {
			add(r, 1, c);
			add(l, 1, c1);
			mx++;
		}
		else {
			printf("%lld\n", mx - sum(l - 1, c) - (sum(n, c1) - sum(r, c1)));
		}
	}
	return 0;
}

                        P2023 [AHOI2009] 维护序列

 P2023 [AHOI2009] 维护序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目背景

老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。

题目描述

有一个长为 n 的数列 {an​},有如下三种操作形式:

  1. 格式 1 t g c,表示把所有满足t≤i≤g 的ai​ 改为ai​×c ;
  2. 格式 2 t g c 表示把所有满足 t≤i≤g 的ai​ 改为 ai​+c ;
  3. 格式 3 t g 询问所有满足t≤i≤g 的 ai​ 的和,由于答案可能很大,你只需输出这个数模 p 的值。

输入格式

第一行两个整数 n 和 p。

第二行含有 n 个非负整数,表示数列 {ai​} 。

第三行有一个整数 m,表示操作总数。

从第四行开始每行描述一个操作,同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。

输出格式

对每个操作 3,按照它在输入中出现的顺序,依次输出一行一个整数表示询问结果。

输入输出样例

输入 #1复制

7 43
1 2 3 4 5 6 7
5
1 2 5 5
3 2 4
2 3 7 9
3 1 3
3 4 7

输出 #1复制

2
35
8

说明/提示

样例输入输出 1 解释
  • 初始时数列为 {1,2,3,4,5,6,7}{1,2,3,4,5,6,7}。
  • 经过第 11 次操作后,数列为 {1,10,15,20,25,6,7}{1,10,15,20,25,6,7}。
  • 对第 22 次操作,和为 10+15+20=4510+15+20=45,模 4343 的结果是 22。
  • 经过第 33 次操作后,数列为 {1,10,24,29,34,15,16}{1,10,24,29,34,15,16}。
  • 对第 44 次操作,和为 1+10+24=351+10+24=35,模 4343 的结果是 3535。
  • 对第 55 次操作,和为 29+34+15+16=9429+34+15+16=94,模 4343 的结果是88。
数据规模与约定

测试数据规模如下表所示:

数据点编号123456789,10
n=10100010001000060000700008000090000100000
m=10100010001000060000700008000090000100000

对于全部的测试点,保证0≤p,ai​,c≤109,1≤t≤g≤n。

解析:区间修改,区间查询,双重懒标记(先乘后加)

本题不同于前一道懒标记的题,本题有加法又有乘法,而且不同的转移顺序最终的结果会不一样

举个例子:比如现在有3个数1,2,3

t[1].add+=2;

t[1].sum+=((3−1)+1)∗2;

再给1~3乘上3

所以

t[1].mu∗=3;

再给1~3加上4,那是不是先加再乘

t[1].add+=4;

显然:

操作2之后的式子是:
sum=(a[1]+2)*3+(a[2]+2)*3+(a[3]+2)*3;

如果直接加

式子是:
sum=(a[1]+2+4)*3+(a[2]+2+4)*3+(a[3]+2+4)*3;
   =(a[1]+2)*3+4*3+(a[2]+2)*3+4*3+(a[3]+2)*3+4*3;

 发现这和

sum=(a[1]+2)*3+4+(a[2]+2)*3+4+(a[3]+2)*3+4;

并不等价

而要等价必须这样

sum=(a[1]+2+4/3)*3+(a[2]+2+4/3)*3+(a[3]+2+4/3)*3;

 所以最后应该这么写

sum=(a[1]*3+2*3+4)+(a[2]*3+2*3+4)+(a[3]*3+2*3+4);

 注:此题解参考自:题解 P3373 【【模板】线段树 2】 - milkfilling 的博客 - 洛谷博客 (luogu.com.cn)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
struct Tree {
	int l, r;
	LL sum, add,mu;
}tree[4 * N];
int arr[N], n, m;

void build(int p, int l, int r) {
	tree[p].l = l, tree[p].r = r, tree[p].mu = 1, tree[p].add = 0;
	if (l == r) {
		tree[p].sum = arr[l];
		return;
	}
	LL mid = (l + r) / 2;
	build(p * 2, l, mid);
	build(p * 2 + 1, mid + 1, r);
	tree[p].sum = (tree[2 * p].sum + tree[2 * p + 1].sum)%m;
	
}
//void spread(int p) {
//	if (tree[p].add || tree[p].mu != 1) {
//		tree[2 * p].sum *= tree[p].mu;
//		tree[p * 2].sum %= m;
//		tree[p * 2].sum += (LL)(tree[p].add % m * (tree[p * 2].r - tree[p * 2].l + 1) % m) % m;
//		tree[p * 2].sum %= m;
//		tree[p * 2].add = (tree[p * 2].add * (tree[p].mu % m)) % m;
//		tree[p * 2].add += tree[p].add % m;
//		tree[p * 2].add %= m;
//		tree[p * 2].mu *= tree[p].mu;
//		tree[p * 2].mu %= m;
//
//		tree[2 * p+1].sum *= tree[p].mu;
//		tree[p * 2+1].sum %= m;
//		tree[p * 2+1].sum += (LL)(tree[p].add % m * (tree[p * 2+1].r - tree[p * 2+1].l + 1) % m) % m;
//		tree[p * 2+1].sum %= m;
//		tree[p * 2 + 1].add = (tree[p * 2+1].add * tree[p].mu ) % m;
//		tree[p * 2+1].add += tree[p].add % m;
//		tree[p * 2+1].add %= m;
//		tree[p * 2+1].mu *= tree[p].mu;
//		tree[p * 2+1].mu %= m;
//		
//		tree[p].add = 0;
//		tree[p].mu = 1;
//	}
//}
void spread(LL p)
{
	if (tree[p].add || tree[p].mu != 1) {
		tree[p * 2].sum = (tree[p * 2].sum * tree[p].mu) % m;
		tree[p * 2 + 1].sum = (tree[p * 2 + 1].sum * tree[p].mu) % m;
		tree[p * 2].sum = (tree[p * 2].sum + tree[p].add * (tree[p * 2].r - tree[p * 2].l + 1)) % m;
		tree[p * 2+1].sum = (tree[p * 2+1].sum + tree[p].add * (tree[p * 2+1].r - tree[p * 2+1].l + 1)) % m;
		tree[p * 2].mu = (tree[p * 2].mu * tree[p].mu) % m;
		tree[p * 2+1].mu = (tree[p * 2+1].mu * tree[p].mu) % m;
		tree[p * 2].add = (tree[p * 2].add * tree[p].mu) % m;
		tree[p * 2+1].add = (tree[p * 2+1].add * tree[p].mu) % m;
		tree[p * 2].add = (tree[p * 2].add + tree[p].add) % m;
		tree[p * 2+1].add = (tree[p * 2+1].add + tree[p].add) % m;
		tree[p].add = 0;
		tree[p].mu = 1;
	}
}
void cheng(int p, int l, int r, int d) {
	if (l <= tree[p].l && r >= tree[p].r) {
		d %= m;
		tree[p].sum *= (LL)(d);
		tree[p].mu *= (LL)(d);
		tree[p].add *= (LL)(d);
		tree[p].sum %= m;
		tree[p].mu %= m;
		tree[p].add %= m;
		return;
	}
	spread(p);
	LL mid = (tree[p].l + tree[p].r) / 2;
	if (l <= mid)cheng(p * 2, l, r, d);
	if (r > mid)cheng(p * 2 + 1, l, r, d);
	tree[p].sum = (tree[p * 2].sum + tree[p * 2 + 1].sum) % m;
}

void jia(int p, int l, int r, int d) {
	if (l <= tree[p].l && r >= tree[p].r) {
		d %= m;
		tree[p].sum = (LL)((tree[p].sum + (d * (tree[p].r - tree[p].l + 1) % m) % m) % m);
		tree[p].add = (LL)((tree[p].add + d) % m);
		return;
	}
	spread(p);
	LL mid = (tree[p].r + tree[p].l) / 2;
	if (l <= mid)jia(p * 2, l, r, d);
	if (r > mid)jia(p * 2 + 1, l, r, d);
	tree[p].sum = (tree[p * 2].sum + tree[p * 2 + 1].sum) % m;
}

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

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		scanf("%d", &arr[i]);
	}
	build(1, 1, n);
	int num;
	cin >> num;
	while (num--) {
		int op;
		int l, r, d;
		scanf("%d%d%d", &op, &l, &r);
		if (op == 1) {
			scanf("%d", &d);
			cheng(1,l,r,d);
		}
		else if (op == 2) {
			scanf("%d", &d);
			jia(1,l,r,d);
		}
		else {
			printf("%lld\n", ask(1, l, r));
		}
	}
	return 0;
}

                        花神游历各国-线段树

 Contest (nefu.edu.cn)

Description

花神喜欢步行游历各国,顺便虐爆各地竞赛。花神有一条游览路线,它是线型的,也就是说,所有游历国家呈一条线的形状排列,花神对每个国家都有一个喜欢程度(当然花神并不一定喜欢所有国家)。
每一次旅行中,花神会选择一条旅游路线,它在那一串国家中是连续的一段,这次旅行带来的开心值是这些国家的喜欢度的总和,当然花神对这些国家的喜欢程序并不是恒定的,有时会突然对某些国家产生反感,使他对这些国家的喜欢度由t 变为sqrt(t),
(可能是花神虐爆了那些国家的 OI,从而感到乏味)。现在给出花神每次的旅行路线,以及开心度的变化,请求出花神每次旅行的开心值。

Input

一行是一个整数 N,表示有 N 个国家;
第二行有 N 个空格隔开的整数,表示每个国家的初始喜欢度ti
第三行是一个整数 M,表示有 M 条信息要处理;
第四行到最后,每行三个整数 x,l,r,当 x=1 时询问游历国家 l 到 r 的开心值总和,当 x=2 时国家 l 到 r 中每个国家的喜欢度由ti变成sqrt(ti);

​​ 
​
​​  

Output

每次 x=1 时,每行一个整数。表示这次旅行的开心度。

Sample Input

4
1 100 5 5
5
1 1 2
2 1 2
1 1 2
2 2 3
1 1 4

Sample Output

101
11
11

Hint

建议使用 sqrt 函数,且向下取整。n,m<=2e5,1<=l<=r<=n;0<=ti<=1e9,注意:开始时的ti可以取0,0不允许开平方的,要标记为不用处理!

Source

BZOJ 3211, loj10128,奥赛一本通

解析:

这道题自己没想出来

由于这个1e8的数字最多只能开根号6次,复杂度最高n*nlog(n);

没跑满,能过

直接进行开根号到叶子节点,然后不断更新。复杂度并不高,但是注意的是,如果当前节点已经是1或者0那么就不要处理了,在更新的过程中多加一个判断修改一下这个题目就可以AC了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 2e5 + 5;

struct tree {
	LL l, r;
	LL sum, flag;
}t[4*N];
int n, m;
int arr[N];

void build(int p, int l, int r) {
	t[p].l = l, t[p].r = r;
	if (t[p].l == t[p].r) {
		t[p].sum = arr[l];
		t[p].flag = arr[l] <= 1 ? 0 : 1;
		return;
	}
	LL mid = (l+r) / 2;
	build(p * 2, l, mid);
	build(p * 2 + 1, mid + 1,r);
	t[p].sum = t[p * 2].sum + t[p * 2 + 1].sum;
	t[p].flag = max(t[p * 2].flag, t[p * 2 + 1].flag);
}

void change(int p, int l, int r) {
	if (t[p].flag == 0)return;
	if (t[p].l == t[p].r) {
		if (t[p].flag) {
			t[p].sum = sqrt(t[p].sum);
			t[p].flag = t[p].sum <= 1 ? 0 : 1;
		}
		return;
	}
	LL mid = (t[p].l + t[p].r) / 2;
	if (l <= mid)change(p * 2, l, r);
	if (r > mid)change(p * 2 + 1, l, r);
	t[p].sum = t[p * 2].sum + t[p * 2 + 1].sum;
	t[p].flag = max(t[p * 2].flag, t[p * 2 + 1].flag);
}

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

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &arr[i]);
	}
	build(1, 1,n);
	scanf("%d", &m);
	int op,l,r;
	while (m--) {
		scanf("%d%d%d", &op, & l, &r);
		if (op == 2) {
			change(1, l, r);
		}
		else {
			printf("%lld\n", ask(1, l, r));
		}
	}
	return 0;
}

                                区间求和-线段树

Contest (nefu.edu.cn)

Problem:H
Time Limit:2000ms
Memory Limit:165535K

Description

有一个序列包含n个正整数,现在有m次询问,每次询问为:求(L,R)的区间中小于等于K的数的和?

Input

输入包含多组数据。每组数据,第一行为n,表示这个整数序列长为n(1<=n<=1e5)。第二行输入整数序列x1,x2,……,xn(1<=xi<=1e9)。第三行输入m(1<=m<=1e5)。接下来m行,每行输入L,R,K(1<=L<=R<=n,0<=K<=1e9)。

Output

每组数据首先输出“Case #x:”,x从1开始,然后m行输出每次询问的结果,具体格式请参考样例。

Sample Input

6
2 5 3 4 7 6
3
2 5 4
3 6 5
1 4 3

Sample Output

Case #1:
7
7
5

Hint

 

Source

CJJ

解析:单点修改,区间查询,离线处理

将题目所给的n个数和m个数字k全部存入数组arr中:
1.存n个数时还要将n个数的为值w存入,op==1代表当前信息是n个数
2.存入m个k时也要讲对应的信息存入,op==0代表当前信息为k
w带表第几个k,同相较于n个数,这k个数还要将k测量的范围l,r存入

然后arr按照v从小到大进行排序

接着在op==1时对线段树进行单点修改
这样可以保证在访问到k时线段树中的所有叶子节点的值均小于k,所以只要选择对应的区间范围内的sum就行了

 详细解析请查看马家沟老三的题解:Problem - 1328 (nefu.edu.cn)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>

using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
int n, m;
struct st {
	int l, r, k,num;
}arr[4*N];

struct st1 {
	int l,r;
	LL sum;
}tr[N*4];

typedef struct st2 {
	LL v, num;
}st2;

int cmp(const struct st& a, const struct st& b) {
	if (a.k == b.k)
		return a.num < b.num;
	return a.k < b.k;
}

void build(int p, int l, int r) {
	tr[p].l = l, tr[p].r = r;
	if (l == r) {
		return;
	}
	LL mid = (l + r) / 2;
	if (l <= mid)
		build(p * 2, l, mid);
	if (r > mid)
		build(p * 2 + 1, mid + 1, r);
}

void change(int p, int y, int x) {
	if (tr[p].l == tr[p].r) {
		tr[p].sum = (LL)x;
		return;
	}
	LL mid = (tr[p].l + tr[p].r) / 2;
	if (y <= mid)change(p * 2, y, x);
	if (y > mid)change(p * 2 + 1, y, x);
	tr[p].sum = tr[p * 2].sum + tr[p * 2 + 1].sum;
}

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

int cmp1(const st2& a, const st2& b) {
	return a.num < b.num;
}

int main() {
	int cnt = 0;
	while (scanf("%d",&n)!=EOF) {
		/*cin >> n ;*/
		memset(tr, 0, sizeof(tr));
		memset(arr, 0, sizeof(arr));
		cnt++;
		for (int i = 1; i <= n; i++) {
			scanf("%d", &arr[i].k);
			arr[i].num = i;
		}
		scanf("%d", &m);
		for (int i = n + 1; i <= m + n; i++) {
			scanf("%d%d%d", &arr[i].l, &arr[i].r, &arr[i].k);
			arr[i].num = i;
		}
		sort(arr + 1, arr + 1 + n + m, cmp);

		/*for (int i = 1; i <= n + m; i++) {
			cout << arr[i].l << " " << arr[i].r << " " << arr[i].num << " " << arr[i].k << endl;
		}
		cout << endl;*/
		build(1, 1, n);
		vector<st2>p;
		for (int i = 1; i <= n + m; i++) {
			if (arr[i].l==0) {
				change(1, arr[i].num, arr[i].k);
			}
			else {
				LL t = ask(1, arr[i].l, arr[i].r);
				p.push_back({ t,arr[i].num });
			}
		}
		sort(p.begin(), p.end(), cmp1);
		printf("Case #%d:\n", cnt);
		for (int i = 0; i < m; i++) {
			printf("%lld\n", p[i].v);
		}
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值