线。段。树--树状数组-主席树

本文介绍了线段树和树状数组在处理区间加法和查询操作中的应用,包括线段树的建立、查询、更新,以及树状数组的初始化、构建和区间查询。此外,还探讨了线段树在处理区间最大值、求和、乘法操作等实际问题中的使用,并提供了相关代码示例。文章通过多个实例展示了线段树和树状数组在动态维护区间信息时的高效性。
摘要由CSDN通过智能技术生成

简单了解一下线段树

以前写过的内容,搬运过来

线段树的应用场景:满足区间加法性质且多次查询,什么是区间加法性质,比如最大值,求和,树状数组、线段树、主席树依次。

线段树框架:建树--查询--更新。。。懒标记

 代码关于下标,原始数据下标,节点下标,节点表示的区间[l,r]

原始数组下标从0/1开始,节点表示区间的意义,节点下标与原始数组下标的关系

 假如不考虑lazy标记,只实现查询功能

 

typedef long long ll;
const int maxn = 1e5;
int a[maxn];
int queSum[4 * maxn];
void initiTree() {
	memset(queSum, 0, sizeof(queSum));
}
void buildTree(int Le, int Ri, int indexRoot) {
	if (Le == Ri) {
		queSum[indexRoot] = a[Le];
		return;
	}
	int mid = Le + (Ri - Le) / 2;
	buildTree(indexRoot << 1, Le, mid);
	buildTree(indexRoot << 1 | 1, mid + 1, Ri);
	queSum[indexRoot] = queSum[indexRoot << 1] + queSum[indexRoot << 1 | 1];
	return;
}
ll queryTree(int indexRoot, int Le, int Ri, int le, int ri) {
	if (le <= Le && ri >= Ri)return queSum[indexRoot];
	int mid = Le + (Ri - Le) / 2;
	ll sum = 0;
	if (mid >= le)sum += queryTree(indexRoot << 1, Le, mid, le, ri);
	if (mid < ri)sum += queryTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri);
	return sum;
}

考虑lazy标记

typedef long long ll;
const int maxn = 1e5;
int a[maxn];
int queSum[4 * maxn];
int lazy[4 * maxn];
void initiTree() {
	memset(queSum, 0, sizeof(queSum));
	memset(lazy, 0, sizeof(lazy));
}
void pushDown(int indexRoot, int Le, int Ri) {
	if (lazy[indexRoot]) {
		int mid = Le + (Ri - Le) / 2;
		lazy[indexRoot << 1] += lazy[indexRoot];
		lazy[indexRoot << 1 | 1] += lazy[indexRoot];
		queSum[indexRoot << 1] += 1ll * (mid - Le + 1) * lazy[indexRoot];
		queSum[indexRoot << 1 | 1] += 1ll * (Ri - mid) * lazy[indexRoot];
		lazy[indexRoot] = 0;
	}
}
void updateTree(int indexRoot, int Le, int Ri, int le, int ri,int add) {
	if (le <= Le && ri >= Ri) {
		lazy[indexRoot] += 1ll * add;
		queSum[indexRoot] += 1ll * (Ri - Le + 1) * add;
		return;
	}
	pushDown(indexRoot, Le, Ri);
	int mid = Le + (Ri - Le) / 2;
	if (mid >= le)updateTree(indexRoot << 1, Le, mid, le, ri, add);
	if (mid < ri)updateTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri,add);
	queSum[indexRoot] = queSum[indexRoot << 1] + queSum[indexRoot << 1 | 1];
	return;
}
void buildTree(int Le, int Ri, int indexRoot) {
	if (Le == Ri) {
		queSum[indexRoot] = a[Le];
		return;
	}
	int mid = Le + (Ri - Le) / 2;
	buildTree(indexRoot << 1, Le, mid);
	buildTree(indexRoot << 1 | 1, mid + 1, Ri);
	queSum[indexRoot] = queSum[indexRoot << 1] + queSum[indexRoot << 1 | 1];
	return;
}
ll queryTree(int indexRoot, int Le, int Ri, int le, int ri) {
	if (le <= Le && ri >= Ri)return queSum[indexRoot];
	pushDown(indexRoot, Le, Ri);
	int mid = Le + (Ri - Le) / 2;
	ll sum = 0;
	if (mid >= le)sum += queryTree(indexRoot << 1, Le, mid, le, ri);
	if (mid < ri)sum += queryTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri);
	return sum;
}

首先,满足区间加法;整体思路就是树的建立与查询,递归,二分;查询更新时,查询与当前查询集合有相交的那部分树。单点操作使用退化的区间操作;查询时,需要先下传标记

在求和这个问题下,lazy的使用和构造较为简单;但是,有时lazy标记不好构造,怎么下传lazy标记事实上不好操作,这是可以抛弃lazy数组,直接更新到叶子节点,加剪枝条件

bool check(int indexRoot, int Le, int Ri) {
	//剪枝条件
}
void updateTreeNoLazy(int indexRoot, int Le, int Ri, int le, int ri) {
	if (Le == Ri) {
		queSum[indexRoot] = 1;//更新方式
		return;
	}
	int mid = Le + (Ri - Le) / 2;
	if (mid >= le && check(indexRoot << 1, Le, mid))updateTreeNoLazy(indexRoot << 1, Le, mid, le, ri);
	if (mid < ri && check(indexRoot << 1 | 1, mid + 1, Ri))updateTreeNoLazy(indexRoot << 1 | 1, mid + 1, Ri, le, ri);
	queSum[indexRoot] = queSum[indexRoot << 1] + queSum[indexRoot << 1 | 1];
	return;
}

查询时间复杂度是logn

线段树模板

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
#include <stack>
#include <map>
#include <vector>
#include <cstring>//memset
#include <string>
using namespace std;
typedef long long ll;
const int maxn = 1e5;
int a[maxn];
int queSum[4 * maxn];
int lazy[4 * maxn];
void initiTree() {
	memset(queSum, 0, sizeof(queSum));
	memset(lazy, 0, sizeof(lazy));
	return;
}
void buildTree(int indexRoot, int Le, int Ri) {
	if (Le == Ri) {
		//queSum[indexRoot] = a[Le];
		cin >> queSum[indexRoot];
		return;
	}
	int mid = Le + (Ri - Le) / 2;
	buildTree(indexRoot << 1, Le, mid);
	buildTree(indexRoot << 1 | 1, mid + 1, Ri);
	queSum[indexRoot] = queSum[indexRoot << 1] + queSum[indexRoot << 1 | 1];
	return;
}
void pushDown(int indexRoot, int Le, int Ri) {
	if (lazy[indexRoot]) {
		int mid = Le + (Ri - Le) / 2;
		//标记下传至子树
		lazy[indexRoot << 1] += lazy[indexRoot];
		lazy[indexRoot << 1 | 1] += lazy[indexRoot];
		//更新子树的根节点信息
		queSum[indexRoot << 1] += 1ll * (mid - Le + 1) * lazy[indexRoot];
		queSum[indexRoot << 1 | 1] += 1ll * (Ri - mid) * lazy[indexRoot];
		//去标记
		lazy[indexRoot] = 0;
	}
	return;
}
ll queryTree(int indexRoot, int Le, int Ri, int le, int ri) {
	if (le <= Le && ri >= Ri) {
		return queSum[indexRoot];
	}
	pushDown(indexRoot, Le, Ri);//下传标记
	int mid = Le + (Ri - Le) / 2;
	ll sum = 0;
	if (mid >= le)sum += queryTree(indexRoot << 1, Le, mid, le, ri);
	if (mid > ri)sum += queryTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri);
	return sum;
}
void updateTree(int indexRoot, int Le, int Ri, int le, int ri,int add) {
	if (le <= Le && ri >= Ri) {
		lazy[indexRoot] += 1ll * add;
		queSum[indexRoot] += 1ll * (Ri - Le + 1) * add;
		return;
	}
	pushDown(indexRoot, Le, Ri);//下传标记
	int mid = Le + (Ri - Le) / 2;
	if (mid >= le)updateTree(indexRoot << 1, Le, mid, le, ri,add);
	if (mid < ri)updateTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri, add);
	queSum[indexRoot] = queSum[indexRoot << 1] + queSum[indexRoot << 1 | 1];
	return;
}
bool check(int indexRoot, int Le, int Ri) {
	//剪枝条件
	return false;
}
void updateTreeNoLazy(int indexRoot, int Le, int Ri, int le, int ri) {
	if (Le == Ri) {
		queSum[indexRoot] = 1;//更新方式
		return;
	}
	int mid = Le + (Ri - Le) / 2;
	if (mid >= le && check(indexRoot << 1, Le, mid))updateTreeNoLazy(indexRoot << 1, Le, mid, le, ri);
	if(mid<ri&&check(indexRoot<<1|1,mid+1,Ri))updateTreeNoLazy(indexRoot<<1|1,mid+1,Ri,le,ri);
	queSum[indexRoot] = queSum[indexRoot << 1] + queSum[indexRoot << 1 | 1];
	return;
}
int main() {
	initiTree();
	buildTree(1,1, 8);
	return 0;
}

敌兵布阵

C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。
中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:"你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:"我知错了。。。"但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的.

Input

第一行一个整数T,表示有T组数据。
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
每组数据最多有40000条命令

Output

对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。

Sample

InputcopyOutputcopy
 
1 
10 
1 2 3 4 5 6 7 8 9 10 
Query 1 3 
Add 3 6 
Query 2 7 
Sub 10 2 
Add 6 3 
Query 3 10 
End 
 
Case 1: 
6 
33 
59

下面代码输出格式可能有问题

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
#include <stack>
#include <map>
#include <vector>
#include <cstring>//memset
#include <string>
using namespace std;
typedef long long ll;
const int maxn = 1e5;
int a[maxn];
ll queSum[4 * maxn];
int lazy[4 * maxn];
void initiTree() {
	memset(queSum, 0, sizeof(queSum));
	memset(lazy, 0, sizeof(lazy));
	return;
}
void pushDown(int indexRoot, int Le, int Ri) {
	if (lazy[indexRoot]) {
		int mid = Le + (Ri - Le) / 2;
		queSum[indexRoot << 1] += 1ll * (mid - Le + 1) * lazy[indexRoot];
		queSum[indexRoot << 1 | 1] += 1ll * (Ri - mid) * lazy[indexRoot];
		lazy[indexRoot << 1] += lazy[indexRoot];
		lazy[indexRoot << 1 | 1] += lazy[indexRoot];
		return;
	}
	return;
}
void buildTree(int indexRoot, int Le, int Ri) {
	if (Le == Ri) {
		//queSum[indexRoot] = a[Le];
		cin >> queSum[indexRoot];
		return;
	}
	int mid = Le + (Ri - Le) / 2;
	buildTree(indexRoot << 1, Le, mid);
	buildTree(indexRoot << 1 | 1, mid + 1, Ri);
	queSum[indexRoot] = queSum[indexRoot << 1] + queSum[indexRoot << 1 | 1];
	return;
}
void updateTree(int indexRoot, int Le, int Ri, int le, int ri,int add) {
	if (le <= Le && ri >= Ri) {
		queSum[indexRoot] += 1ll * (Ri - Le + 1) * add;
		lazy[indexRoot] += add;
		return;
	}
	pushDown(indexRoot, Le, Ri);
	int mid = Le + (Ri - Le) / 2;
	if (mid >= le)updateTree(indexRoot << 1, Le, mid, le, ri, add);
	if (mid < ri)updateTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri, add);
	queSum[indexRoot] = queSum[indexRoot << 1] + queSum[indexRoot << 1 | 1];
	return;
}
bool check(int indexRoot, int Le, int Ri) {
	//剪枝
	return false;
}
void updateTreeNoLazy(int indexRoot, int Le, int Ri, int le, int ri) {
	if (Le == Ri) {
		queSum[indexRoot] = 1;//更新方式
		return;
	}
	int mid = Le + (Ri - Le) / 2;
	if (mid >= le&&check(indexRoot<<1,Le,mid))updateTreeNoLazy(indexRoot << 1, Le, mid, le, ri);
	if (mid < ri&&check(indexRoot<<1|1,mid+1,Ri))updateTreeNoLazy(indexRoot << 1 | 1, mid + 1, Ri, le, ri);
	queSum[indexRoot] = queSum[indexRoot << 1] + queSum[indexRoot << 1 | 1];
	return;
}
ll queryTree(int indexRoot, int Le, int Ri, int le, int ri) {
	if (le <= Le && ri >= Ri) {
		return queSum[indexRoot];
	}
	pushDown(indexRoot, Le, Ri);
	int mid = Le + (Ri - Le) / 2;
	ll sum = 0;
	if (mid >= le)sum += queryTree(indexRoot << 1, Le, mid, le, ri);
	if (mid < ri)sum += queryTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri);
	return sum;
}
int t,n;

int main() {
	cin >> t ;
	int times = 1;
	while (t--) {
		cin >> n;
		initiTree();
		buildTree(1, 1, n);
		cout << "Case " << times << ":\n";
		char order[10];
		while (true) {
			cin >> order;
			if (order[0] == 'E')break;
			int i, j;
			cin >> i >> j;
			if (order[0] == 'A')updateTree(1, 1, n, i, i, j);
			if (order[0] == 'S')updateTree(1, 1, n, i, i, -1 * j);
			if (order[0] == 'Q') cout<< queryTree(1, 1, n, i, j) << endl;
		}
		times++;
	}
	
	return 0;
}

给你一个长为n的序列,有m个操作,全局加,或者查询区间最大子段和。

输入描述:

第一行两个数n,m
之后一行n个数表示这个序列
之后m行,每行一个操作
1 x : 所有数都加上x
2 l r : 查询区间[l,r]内的最大子段和(可以不选数)

输出描述:

对于每个2种类操作,输出一行一个数表示答案

示例1

输入

5 7
-10 -3 -2 -4 -5
2 2 4
1 5
2 2 4
1 3
2 1 5
1 2
2 3 5

输出

0
6
18
19

备注:

1 <= n <= 300000
1 <= m <= 600000
序列中的数绝对值<= 2000000000
1操作中的x的绝对值<= 50000000

考点,区间加法性质,区间操作,区间查询---点查询就不要记了,直接用退化版的区间查询

待续---20220420之前会写这几题---20220418记--树状数组--主席树后续再写

20220426更

错误代码如下

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 500000;
int a[maxn];
int que[maxn * 4];
int Lazy[maxn * 4];
void pushDown(int indexRoot, int Le, int Ri)
{
	int tmp = Lazy[indexRoot];
	if (tmp) {
		int mid = Le + (Ri - Le) / 2;
		que[indexRoot << 1] += tmp*(mid-Le+1);
		que[indexRoot << 1 |1| 1] += tmp*(Ri-mid);
		Lazy[indexRoot << 1] += tmp;
		Lazy[indexRoot << 1 | 1] += tmp;
		Lazy[indexRoot] = 0;
	}
}
void buildTree(int indexRoot, int Le, int Ri) {
	if (Le == Ri) {
		que[indexRoot] = a[Le];
	}
	else {
		int mid = Le + (Ri - Le) / 2;
		buildTree(indexRoot << 1, Le, mid);
		buildTree(indexRoot << 1 | 1, mid + 1, Ri);
		que[indexRoot] = max(max(que[indexRoot << 1], que[indexRoot << 1 | 1]), que[indexRoot << 1] + que[indexRoot << 1 | 1]);
	}
}
void updateTree(int indexRoot, int Le, int Ri, int le, int ri, int add) {
	if (le <= Le && ri >= Ri) {
		que[indexRoot] += add * (Ri - Le + 1);
		Lazy[indexRoot] += add;
	}
	else {
		int mid = Le + (Ri - Le) / 2;
		pushDown(indexRoot, Le, Ri);
		if (le <= mid)updateTree(indexRoot << 1, Le, mid, le, ri, add);
		if (ri > mid)updateTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri, add);
		que[indexRoot] = max(max(que[indexRoot << 1], que[indexRoot << 1 | 1]), que[indexRoot << 1] + que[indexRoot << 1 | 1]);
	}
}
ll queryTree(int indexRoot, int Le, int Ri, int le, int ri) {
	if (le <= Le && ri >= Ri) {
		return que[indexRoot];
	}
	else {
		int mid = Le + (Ri - Le) / 2;
		pushDown(indexRoot, Le, Ri);
		ll leftAns = 0;
		ll rightAns = 0;
		if (le <= mid)leftAns = queryTree(indexRoot << 1, Le, mid, le, ri);
		if (ri > mid)rightAns = queryTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri);
		ll ans = max(leftAns, rightAns);
		ans = max(ans, leftAns + rightAns);
		return ans;
	}
}
int main() {
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	while (m--) {
		int choice, add, ri;
		scanf("%d %d", &choice, &add);
		if (choice == 1) {
			updateTree(1, 1, n, 1, n, add);
		}
		if (choice == 2) {
			scanf("%d", &ri);
			printf("%lld\n", queryTree(1, 1, n, add, ri));
		}
	}

	return 0;
}

找错误,更新操作出错--Lazy标记更新不好写,负数不能单纯考虑加,lazy标记不好写,参考上面模板中不用lazy标记的情况,updateTreeNoLazy,每次直接更新叶节点,由于是全局更新,不设置check约束了-----由于找不到评测的平台,代码只跑了样例,后续找到再更

代码如下

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 500000;
//int a[maxn];
ll que[maxn * 4];
void buildTree(int indexRoot, int Le, int Ri) {
	if (Le == Ri) {
		//que[indexRoot] = a[Le];
		scanf("%lld", &que[indexRoot]);
	}
	else {
		int mid = Le + (Ri - Le) / 2;
		buildTree(indexRoot << 1, Le, mid);
		buildTree(indexRoot << 1 | 1, mid + 1, Ri);
		que[indexRoot] = max(max(que[indexRoot << 1], que[indexRoot << 1 | 1]), que[indexRoot << 1] + que[indexRoot << 1 | 1]);
	}
}
void updateTree(int indexRoot, int Le, int Ri, int le, int ri, int add) {
	if (Le == Ri) {
		que[indexRoot] += add;//更新方式
	}
	else {
		int mid = Le + (Ri - Le) / 2;
		if (le <= mid)updateTree(indexRoot << 1, Le, mid, le, ri, add);
		if (ri > mid)updateTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri, add);
		que[indexRoot] = max(max(que[indexRoot << 1], que[indexRoot << 1 | 1]), que[indexRoot << 1] + que[indexRoot << 1 | 1]);
	}
}
ll queryTree(int indexRoot, int Le, int Ri, int le, int ri) {
	if (le <= Le && ri >= Ri) {
		return que[indexRoot];
	}
	else {
		int mid = Le + (Ri - Le) / 2;
		ll leftAns = 0;
		ll rightAns = 0;
		if (le <= mid)leftAns = queryTree(indexRoot << 1, Le, mid, le, ri);
		if (ri > mid)rightAns = queryTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri);
		ll ans = max(leftAns, rightAns);
		ans = max(ans, leftAns + rightAns);
		return ans;
	}
}
int main() {
	int n, m;
	cin >> n >> m;
	buildTree(1, 1, n);
	while (m--) {
		int choice, add, ri;
		scanf("%d %d", &choice, &add);
		if (choice == 1) {
			updateTree(1, 1, n, 1, n, add);
		}
		if (choice == 2) {
			scanf("%d", &ri);
			printf("%lld\n", queryTree(1, 1, n, add, ri));
		}
	}

	return 0;
}

Problem - 1754http://acm.hdu.edu.cn/showproblem.php?pid=1754

Problem Description

很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。
这让很多学生很反感。

不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩。

Input

本题目包含多组测试,请处理到文件结束。
在每个测试的第一行,有两个正整数 N 和 M ( 0<N<=200000,0<M<5000 ),分别代表学生的数目和操作的数目。
学生ID编号分别从1编到N。
第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩。
接下来有M行。每一行有一个字符 C (只取'Q'或'U') ,和两个正整数A,B。
当C为'Q'的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。
当C为'U'的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。

Output

对于每一次询问操作,在一行里面输出最高成绩。

Sample Input

5 6

1 2 3 4 5

Q 1 5

U 3 6

Q 3 4

Q 4 5

U 2 9

Q 1 5

Sample Output

5

6

5

9

如何接受输入,即接受字符和两个数字,设置char choice; int le,ri;scanf("%c %d %d",&choice,&le,&ri);出现乱码。如下图

原因:不知道

 

 解决办法,不用char接受输入,用字符串,如图

 全部代码如下----注册还没成功,还没有经过评测。

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 200000;
ll a[maxn];
ll que[maxn * 4];
int Lazy[maxn * 4];
void buildTree(int indexRoot, int Le, int Ri) {
	if (Le == Ri)scanf("%d", &que[indexRoot]);
	else {
		int mid = Le + (Ri - Le) / 2;
		buildTree(indexRoot << 1, Le, mid);
		buildTree(indexRoot << 1 | 1, mid + 1, Ri);
		que[indexRoot] = max(que[indexRoot << 1], que[indexRoot << 1 | 1]);
	}
}
void updateTree(int indexRoot, int Le, int Ri, int le, int ri,int a) {
	if (le <= Le && ri >= Ri)que[indexRoot] = a;
	else {
		int mid = Le + (Ri - Le) / 2;
		if (le <= mid)updateTree(indexRoot << 1, Le, mid, le, ri, a);
		if (ri > mid)updateTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri, a);
		que[indexRoot] = max(que[indexRoot << 1], que[indexRoot << 1 | 1]);
	}
}
ll queryTree(int indexRoot, int Le, int Ri, int le, int ri) {
	if (le <= Le && ri >= Ri)return que[indexRoot];
	else {
		int mid = Le + (Ri - Le) / 2;
		ll tmpMax = -10;
		if (le <= mid)tmpMax = max(tmpMax, queryTree(indexRoot << 1, Le, mid, le, ri));
		if (ri > mid)tmpMax = max(tmpMax, queryTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri));
		return tmpMax;
	}
}
int main() {
	int n, m;
	char choice[3];
	int le, ri;
	scanf("%d %d", &n, &m);
	buildTree(1, 1, n);
	while(m--) {
		scanf("%s", choice);
		scanf("%d %d", &le, &ri);
		if (choice[0] == 'Q')printf("%lld\n", queryTree(1, 1, n, le, ri));
		else if (choice[0] =='U')updateTree(1, 1, n, le, le, ri);
	}
	return 0;
}

线段树 (经典题目合集)_繁凡さん的博客-CSDN博客_线段树经典题目P3372 【模板】线段树 1#include<bits/stdc++.h>using namespace std;typedef long long ll;const ll N=1e5+7;const ll mod=2147483647;ll n,m;struct node{ ll l,r,sum,lz;}tree[N];ll arr[N];void b...https://blog.csdn.net/weixin_45697774/article/details/104271306

 线段树乘法操作模板,求连续k个数的最大乘积

本题转换一下思路,求连续k个数的最大乘积,首先,这符合区间加法,其次,我们可以把这连续的k个数像求解连续的k个数的和一样,从线段树上分别查询到,再依次比较,需要从头到尾遍历区间[1,n]。

另注,如果本体是求连续的k个数的和的最大值,该怎么求,可以想到用区间和,用线段树,换成乘积是一样的。代码我就不写了,直接粘贴一份。后面有时间再自己编写一份。还是自己写一份吧,感觉不是很复杂。

代码如下,该题目是

 无法提交测试,给出线段树解法,能过样例,不知到能不能AC

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 200000;
const int mod = 998244353;
ll a[maxn];
ll que[maxn * 4];
void buildTree(int indexRoot, int Le, int Ri) {
	if (Le == Ri)que[indexRoot] = a[Le]%mod;//scanf("%lld", &que[indexRoot]);
	else {
		int mid = Le + (Ri - Le) / 2;
		buildTree(indexRoot << 1, Le, mid);
		buildTree(indexRoot << 1 | 1, mid + 1, Ri);
		que[indexRoot] = ((que[indexRoot << 1]%mod) * (que[indexRoot << 1 | 1]%mod))%mod;
	}
}
ll queryTree(int indexRoot, int Le, int Ri,int le,int ri) {
	if (le <= Le && ri >= Ri)return que[indexRoot];
	else {
		int mid = Le + (Ri - Le) / 2;
		ll ans = 1;
		if (le <= mid)ans =ans* queryTree(indexRoot << 1, Le, mid, le, ri)%mod;
		if (ri > mid)ans = ans*(queryTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri)%mod)%mod;
		return ans;
	}
}

int main() {
	int n, k;
	scanf("%d %d", &n, &k);
	ll ans = 0;
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	buildTree(1, 1, n);
	for (int i = 1; i + k - 1 <= n; i++)
		ans = max(ans, queryTree(1, 1, n, i, i + k - 1));
	printf("%lld\n", ans);
	return 0;
}

有链接说可以用逆元,链接如下牛客:子段乘积 (逆元or线段树)_Vain957的博客-CSDN博客

上帝造题的七分钟---洛谷题目

上帝造题的七分钟 - 洛谷https://www.luogu.com.cn/problem/P4514

题目背景

裸体就意味着身体。

题目描述

“第一分钟,X 说,要有矩阵,于是便有了一个里面写满了 0 的 n×m 矩阵。

第二分钟,L 说,要能修改,于是便有了将左上角为 (a,b),右下角为 (c,d) 的一个矩形区域内的全部数字加上一个值的操作。

第三分钟,k 说,要能查询,于是便有了求给定矩形区域内的全部数字和的操作。

第四分钟,彩虹喵说,要基于二叉树的数据结构,于是便有了数据范围。

第五分钟,和雪说,要有耐心,于是便有了时间限制。

第六分钟,吃钢琴男说,要省点事,于是便有了保证运算过程中及最终结果均不超过 3232 位有符号整数类型的表示范围的限制。

第七分钟,这道题终于造完了,然而,造题的神牛们再也不想写这道题的程序了。”。

——《上帝造裸题的七分钟》。

所以这个神圣的任务就交给你了。

输入格式

输入数据的第一行为 X n m,代表矩阵大小为 n×m。
从输入数据的第二行开始到文件尾的每一行会出现以下两种操作:

  • L a b c d delta —— 代表将 (a,b),(c,d)为顶点的矩形区域内的所有数字加上 delta。
  • k a b c d —— 代表求 (a,b),(c,d)为顶点的矩形区域内所有数字的和。

请注意,k 为小写。

输出格式

针对每个 k 操作,在单独的一行输出答案。

输入输出样例

输入 复制

X 4 4
L 1 1 3 3 2
L 2 2 4 4 1
k 2 2 3 3

输出 复制

12

说明/提示

对于 110% 的数据,1≤n≤16,1≤m≤16, 操作不超过 200 个。

对于 60% 的数据,1≤n≤512,1≤m≤512。

对于 100% 的数据,1≤n≤2048,1≤m≤2048,−500≤delta≤500,操作不超过 2×105 个,保证运算过程中及最终结果均不超过 32 位带符号整数类型的表示范围。

二维线段树---不会写

二维树状数组---树状数组

树状数组简介链接树状数组 详解 - 林夕-梦 - 博客园

 

模板

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 100000;
ll a[maxn];
ll c[maxn];
int n;
int lowbit(int x){
	return x&(-x);
} 
void Build(int n){
	for (int i = 1;i <= n;i++){
		for(int j = i; j>=i-lowbit(i)+1;j--)
			c[i]+=a[j];
	}
}
ll sum (int n){
	ll ans = 0;
	for(int i = n;i > 0;i-=lowbit(i))//i-lowbit(i)得到的是i的子节点 
		ans+=c[i];
	return ans; 
}
void update(int id,int value){
	for(int i = id;i <= n;i+=lowbit(i))//i+lowbit(i)得到的是i的父节点 
		c[i]+=value;
}
int main(){
	//cout<<"hello world!"<<endl; 
	return 0;
}

---差分与树状数组--- 

 

后续再补

摘自博客题解 P4514 【上帝造题的七分钟】 - kuansoudafahao 的博客 - 洛谷博客https://www.luogu.com.cn/blog/user53167/solution-p4514

 

 

// luogu-judger-enable-o2
#include <cstdio>

int n,m,num,x1,y1,x2,y2;
char c[3];

struct BIT
{
	int tree[2050][2050];
	
	int lowbit(int x) {return x&-x;}
	
	void add(int x,int y,int num)
	{
		for(int i=x; i<=n; i+=lowbit(i))
			for(int j=y; j<=m; j+=lowbit(j))
				tree[i][j]+=num;
	}
	
	int query(int x,int y)
	{
		int res=0;
		for(int i=x; i>=1; i-=lowbit(i))
			for(int j=y; j>=1; j-=lowbit(j))
				res+=tree[i][j];
		return res;
	}
}A,Ai,Aj,Aij;

int read()
{
	int sign=1,res=0;
	char c;
	while((c=getchar())<48||c>57)
		if(c=='-')
			sign=-1;
	if(sign)
		res=c-48;
	while((c=getchar())>=48&&c<=57)
		res=res*10+c-48;
	return res*sign;
}

int Ans(int x,int y)
{
	return A.query(x,y)*(x*y+x+y+1)-
		   Ai.query(x,y)*(y+1)-
		   Aj.query(x,y)*(x+1)+
		   Aij.query(x,y);
}

void Add(int x,int y,int num)
{
	A.add(x,y,num);
	Ai.add(x,y,num*x);
	Aj.add(x,y,num*y);
	Aij.add(x,y,num*x*y);
}

int main()
{
	scanf("X %d %d",&n,&m);
	while(~scanf("%s",&c))
	{
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		if(c[0]=='L')
		{
			scanf("%d",&num);
			Add(x1,y1,num);
			Add(x1,y2+1,-num);
			Add(x2+1,y1,-num);
			Add(x2+1,y2+1,num);
		}else
		{
			printf("%d\n",Ans(x2,y2)-Ans(x1-1,y2)-Ans(x2,y1-1)+Ans(x1-1,y1-1));
		}
	}
	return 0;
}

线段树和树状数组练习题目【数据结构2-2】线段树与树状数组 - 题单 - 洛谷

待更

上帝造题的七分钟 2 / 花神游历各国 - 洛谷https://www.luogu.com.cn/problem/P4145

题目背景

XLk 觉得《上帝造题的七分钟》不太过瘾,于是有了第二部。

题目描述

"第一分钟,X 说,要有数列,于是便给定了一个正整数数列。

第二分钟,L 说,要能修改,于是便有了对一段数中每个数都开平方(下取整)的操作。

第三分钟,k 说,要能查询,于是便有了求一段数的和的操作。

第四分钟,彩虹喵说,要是 noip 难度,于是便有了数据范围。

第五分钟,诗人说,要有韵律,于是便有了时间限制和内存限制。

第六分钟,和雪说,要省点事,于是便有了保证运算过程中及最终结果均不超过 64 位有符号整数类型的表示范围的限制。

第七分钟,这道题终于造完了,然而,造题的神牛们再也不想写这道题的程序了。"

——《上帝造题的七分钟·第二部》

所以这个神圣的任务就交给你了。

输入格式

第一行一个整数 n,代表数列中数的个数。

第二行 n 个正整数,表示初始状态下数列中的数。

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

接下来 m 行每行三个整数 k l r

  • k=0 表示给 [l,r] 中的每个数开平方(下取整)。

  • k=1 表示询问 [l,r] 中各个数的和。

数据中有可能l>r,所以遇到这种情况请交换 l 和 r。

输出格式

对于询问操作,每行输出一个回答。

输入输出样例

输入 

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

输出 

19
7
6

说明/提示

对于 30% 的数据,1≤n,m≤103,数列中的数不超过32767。

对于 100% 的数据,1≤n,m≤105,1≤l,r≤n,数列中的数大于 0,且不超过 10^12。

线段树---线段树--修改和查询

下面的代码只有5/10,后续更

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn = 200000;
ll a[maxn];
ll que[maxn * 4];
ll maxque[maxn * 4];
void buildTree(int indexRoot, int Le, int Ri) {
	if (Le == Ri)que[indexRoot] = a[Le], maxque[indexRoot] = a[Le];
	else {
		int mid = Le + (Ri - Le) / 2;
		buildTree(indexRoot << 1, Le, mid);
		buildTree(indexRoot << 1 | 1, mid + 1, Ri);
		maxque[indexRoot] = max(maxque[indexRoot << 1], maxque[indexRoot << 1 | 1]);
		que[indexRoot] = que[indexRoot << 1] + que[indexRoot << 1 | 1];
	}
}
ll queryTree(int indexRoot, int Le, int Ri, int le, int ri) {
	if (le <= Le && ri >= Ri)return que[indexRoot];
	else {
		int mid = Le + (Ri - Le) / 2;
		ll sum = 0;
		if (le <= mid)sum += queryTree(indexRoot << 1, Le, mid, le, ri);
		if (ri > mid)sum += queryTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri);
		return sum;
	}
}
void updateTree(int indexRoot, int Le, int Ri, int le, int ri) {
	if (Le == Ri) {//叶子节点处更新
		maxque[indexRoot] = sqrt((double)maxque[indexRoot]);
		que[indexRoot] = sqrt(que[indexRoot]);
	}
	else {
		int mid = Le + (Ri - Le) / 2;
		if (le <= mid && maxque[indexRoot << 1] > 1)updateTree(indexRoot << 1, Le, mid, le, ri);
		if (ri > mid && maxque[indexRoot << 1 | 1] > 1)updateTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri);
		que[indexRoot] = que[indexRoot << 1] + que[indexRoot << 1 | 1];
		maxque[indexRoot] = max(maxque[indexRoot << 1], maxque[indexRoot << 1 | 1]);
	}
}
int main() {
	int n, m;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)scanf("%d", &a[i]);
	buildTree(1, 1, n);
	scanf("%d", &m);
	int k, le, ri,tmp;
	while (m--) {
		scanf("%d %d %d", &k, &le, &ri);
		if (le > ri)tmp = le, le = ri, ri = tmp;
		if (k == 0)updateTree(1, 1, n, le, ri);
		else if (k == 1)printf("%lld\n", queryTree(1, 1, n, le, ri));
	}
	return 0;
}

10/10别人的代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 4e5 + 10;
const ll mod=1000000010;
ll n,m,k,arr[N];
struct node
{
    ll l,r,sum,maxn;
}tree[N];
inline void push_up(ll i)
{
    tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;
    tree[i].maxn=max(tree[i*2].maxn,tree[i*2+1].maxn);
}
inline void build(ll i,ll l,ll r)
{
    tree[i].l=l;
    tree[i].r=r;
    if(l==r)
    {
        tree[i].sum=tree[i].maxn=arr[l];
        return ;
    }
    ll mid=(l+r)>>1;
    build(i*2,l,mid);
    build(i*2+1,mid+1,r);
    push_up(i);
}
inline void change(ll i,ll l,ll r)
{
    if(tree[i].l==tree[i].r)
    {
        tree[i].sum=sqrt(tree[i].sum);
        tree[i].maxn=sqrt(tree[i].maxn);
        return ;
    }
    if(tree[i*2].r>=l&&tree[i*2].maxn>1)
        change(i*2,l,r);
    if(tree[i*2+1].l<=r&&tree[i*2+1].maxn>1)
        change(i*2+1,l,r);
    push_up(i);
}
inline ll query(ll i,ll l,ll r)
{
    if(tree[i].l>=l&&tree[i].r<=r)
        return tree[i].sum;
    ll ans=0;
    if(tree[i*2].r>=l)
        ans+=query(i*2,l,r);
    if(tree[i*2+1].l<=r)
        ans+=query(i*2+1,l,r);
    return ans;
}
int main()
{
    ll a,l,r;
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;++i)
        cin>>arr[i];
    build(1,1,n);
    cin>>m;
    while(m--)
    {
        cin>>a>>l>>r;
        if(l>r)swap(l,r);
        if(a==0)
            change(1,l,r);
        else cout<<query(1,l,r)<<endl;
    }
    return 0;
}

并查集和树状数组解法登录 - 洛谷链接

#include <bits/stdc++.h>
#define maxn 100100
#define ll long long
using namespace std;
ll tree[maxn*4],a[maxn];int fa[maxn],m,n,q,l,r,t;
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}//并查集,路径压缩--
void add(int x,ll y){while(x<=n)tree[x]+=y,x+=(x&-x);}
ll qry(int x){ll r=0;while(x)r+=tree[x],x-=(x&-x);return r;}
int main(){
	scanf("%d",&n);for (int i=1;i<=n;i++)
	scanf("%lld",&a[i]),add(i,a[i]),fa[i]=i;scanf("%d",&m);fa[n+1]=n+1;
	while(m--){scanf("%d%d%d",&q,&l,&r);if (l>r) swap(l,r); 
		if (q==1) printf("%lld\n",qry(r)-qry(l-1));
		else for (int i=l;i<=r;add(i,(t=(int)sqrt(a[i]))-a[i]),a[i]=t,fa[i]=(a[i]<=1)?i+1:i,i=(find(i)==i)?i+1:fa[i]);
	}
    //上面这行信息量很大。。。做了单点修改的操作,a数组保存了每个点的实际值,当a[i]<=1的时候,直接跳到下一个点,结束。可以手算一下,就能很快理解了。
}


最大子段和 (nowcoder.com)https://ac.nowcoder.com/acm/problem/52957

给一个长为n的序列,有m次查询操作
查询操作形如 l r L R,表示将序列中值在[L,R]内的位置保留不变,其他的位置变成0时,序列中[l,r]区间内的最大子段和,这个子段可以是空的

输入描述:

第一行两个数n,m
第二行n个数表示这个序列
之后m行,每行四个数l r L R表示一次查询操作
n,m<=1e5,序列中所有数的绝对值<=1e9

输出描述:

输出m行,每行一个数表示答案

示例1

输入

6 5
-1 1 -4 5 -1 4
1 1 4 5
1 1 4 514
2 3 3 3
1 6 -1 5
2 5 2 5

输出

0
0
0
9
5

说明

第四组查询中,值域限制是[-1,5],序列为-1 1 0 5 -1 4,区间[1,6]的最大子段为[2,6],和为9

第五组查询中,值域限制是[2,5],序列为0 0 0 5 0 4,区间[2,5]的最大子段为[4,4](并列),和为5

错误代码,题目的意思是每次查询时修改,每次修改只对该次查询才起作用,即修改不是在原地上的;本题的子段和是连续的,不是求区间上所有正数的和,而是类似于前缀和,求连续的一段作为子段

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 200000;
ll a[maxn];
ll que[maxn * 4];
void buildTree(int indexRoot, int Le, int Ri) {
	if (Le == Ri) {
		que[indexRoot] = a[Le];
		//scanf("%lld", &que[indexRoot]);
	}
	else {
		int mid = Le + (Ri - Le) / 2;
		buildTree(indexRoot << 1, Le, mid);
		buildTree(indexRoot << 1 | 1, mid + 1, Ri);
		que[indexRoot] = max(max(que[indexRoot << 1], que[indexRoot << 1 | 1]), que[indexRoot << 1] + que[indexRoot << 1 | 1]);
	}
}
bool check(int indexRoot, int Low, int Up) {
	if (que[indexRoot] >= Low && que[indexRoot] <= Up)return false;
	else return true;
}
void updateTree(int indexRoot, int Le, int Ri, int low, int up) {
	if (Le == Ri) {
		if(check(indexRoot, low, up))
			que[indexRoot] = 0;//更新方式
	}
	else {
		int mid = Le + (Ri - Le) / 2;
		updateTree(indexRoot << 1, Le, mid, low, up);
		updateTree(indexRoot << 1 | 1, mid + 1, Ri, low, up);
		que[indexRoot] = max(max(que[indexRoot << 1], que[indexRoot << 1 | 1]), que[indexRoot << 1] + que[indexRoot << 1 | 1]);
	}
}
ll queryTree(int indexRoot, int Le, int Ri, int le, int ri) {
	if (le <= Le && ri >= Ri) {
		return que[indexRoot];
	}
	else {
		int mid = Le + (Ri - Le) / 2;
		ll leftAns = 0;
		ll rightAns = 0;
		if (le <= mid)leftAns = queryTree(indexRoot << 1, Le, mid, le, ri);
		if (ri > mid)rightAns = queryTree(indexRoot << 1 | 1, mid + 1, Ri, le, ri);
		ll ans = max(leftAns, rightAns);
		ans = max(ans, leftAns + rightAns);
		return ans;
	}
}
int main() {
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	buildTree(1, 1, n);
	while (m--) {
		int l, r, L, R;
		scanf("%d %d %d %d", &l, &r, &L, &R);
		updateTree(1, 1, n, L, R);
		printf("%lld\n", queryTree(1, 1, n, l, r));
	}

	return 0;
}

题解,别人的AC代码,本题太难,暂缓

//#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5, blksz = 200;
int n, m;
int a[maxn + 10], ls[maxn + 10], rs[maxn + 10], L[maxn + 10], R[maxn + 10];
int posl[maxn + 10], posr[maxn + 10];

struct node {
	int v, id;
	
	bool operator < (const node &t) const {//小于规则 
		return v < t.v;
	}
	
}sl[maxn + 10], sr[maxn + 10];

struct data {
	ll sum, pre, suf, mx;
};
vector<int> c;
vector<vector<data> > d;
data ans[maxn + 10];

data operator + (const data &a, const data &b) {//加法运算符重载 
	data ans; ans.sum = a.sum + b.sum;
	ans.pre = max(a.pre, a.sum + b.pre);
	ans.suf = max(b.suf, b.sum + a.suf);
	ans.mx = max(a.suf + b.pre, max(a.mx, b.mx));
	return ans;
}

void calcans(int l, int r, vector<int> &c, vector<vector<data> > &d) {
	int n = r - l + 1;
	c.resize(n); d.resize(n);//给大小 
	for (int i = 0; i < n; ++i) d[i].resize(n);//再给大小 
	for (int i = 0; i < n; ++i)
		for (int j = 0; j < n; ++j) d[i][j] = (data){0, 0, 0, (ll)-1e18};
	if (l == r) {//叶子节点 
		c[0] = a[l]; d[0][0] = (data){a[l], a[l], a[l], a[l]};
		return;
	}
	vector<int> cl, cr, idl, idr;
	vector<vector<data> > dl, dr;//复制一份 
	int mid = (l + r) >> 1;
	calcans(l, mid, cl, dl);
	calcans(mid + 1, r, cr, dr);
	int nl = (int)cl.size(), nr = (int)cr.size();
	int pl = 0, pr = 0, ccnt = 0;
	idl = cl; idr = cr;
	while (pl != nl || pr != nr) {
		if (pl < nl && (pr == nr || cl[pl] < cr[pr])) {
			idl[pl] = ccnt; c[ccnt++] = cl[pl++];
		} else {
			idr[pr] = ccnt; c[ccnt++] = cr[pr++];
		}
	}
	for (int i = 0; i < nl; ++i)
		for (int j = i; j < nl; ++j)
			for (int k = idl[i]; k > (i ? idl[i - 1] : -1); --k)
				for (int l = idl[j]; l < (j < nl - 1 ? idl[j + 1] : n); ++l) {
					d[k][l] = dl[i][j];
				}
	for (int i = 0; i < nr; ++i)
		for (int j = i; j < nr; ++j)
			for (int k = idr[i]; k > (i ? idr[i - 1] : -1); --k)
				for (int l = idr[j]; l < (j < nr - 1 ? idr[j + 1] : n); ++l) {
					d[k][l] = d[k][l] + dr[i][j];
				}
}

void solve(int l, int r) {
	calcans(l, r, c, d);
	int n = (int)c.size();
	for (int i = 1, p = 0; i <= m; ++i) {
		while (p < n && c[p] < sl[i].v) ++p;
		posl[sl[i].id] = p;
	}
	for (int i = 1, p = 0; i <= m; ++i) {
		while (p < n && c[p] < sr[i].v) ++p;
		posr[sr[i].id] = p;
	}
	for (int i = 1; i <= m; ++i) {
		if (rs[i] < l || ls[i] > r) continue;
		if (ls[i] <= l && rs[i] >= r) {
			int x = posl[i];
			int y = posr[i] - 1;
			if (x <= y) ans[i] = ans[i] + d[x][y];
		} else {
			for (int j = l; j <= r; ++j)
				if (j >= ls[i] && j <= rs[i] && a[j] >= L[i] && a[j] <= R[i])
					ans[i] = ans[i] + (data){a[j], a[j], a[j], a[j]};
		}
	}
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i <= m; ++i) {
		scanf("%d%d%d%d", &ls[i], &rs[i], &L[i], &R[i]);
		sl[i] = (node){L[i], i};
		sr[i] = (node){R[i] + 1, i};
	}
	sort(sl + 1, sl + m + 1);
	sort(sr + 1, sr + m + 1);
	for (int i = 1; i <= n; i += blksz)
		solve(i, min(i + blksz - 1, n));
	for (int i = 1; i <= m; ++i)
		printf("%lld\n", max(ans[i].mx, 0ll));
}

给出一个序列,你的任务是求每次操作之后序列中 (a[j]-a[i])/(j-i)【1<=i<j<=n】的最大值。

操作次数有Q次,每次操作需要将位子p处的数字变成y.

输入描述:

本题包含多组输入,每组输入第一行一个数字n,表示序列的长度。
然后接下来一行输入n个数,表示原先序列的样子。
再接下来一行一个数字Q,表示需要进行的操作的次数。
最后Q行每行两个元素p,y,表示本操作需要将位子p处的数字变成y.
数据范围:
3<=n<=200000
1<=Q<=200000
-1000000000<=a[i]<=1000000000

输出描述:

每组数据输出Q行,每行输出一个浮点数,保留两位小数,表示所求的最大值。

示例1

输入

5 2 4 6 8 10 2 2 5 4 9

5
2 4 6 8 10
2
2 5
4 9

输出

3.00 3.00

3.00
3.00

说明

第一次操作之后的序列变成:2 5 6 8 10
第二次操作之后的序列变成:2 5 6 9 10

备注:

输入只有整形

分析:a[j]-a[i])/(j-i)【1<=i<j<=n】的最大值,单独一个数组元素不是这个所求,不符合线段树定义

不符合区间加法

转化1:求最大值,而最大值一定是相邻两个元素的差求出来的

直观可以看出来,最大值就是相邻两个数求得的;

几何上看,求得像是两点之间的斜率,可知相邻两点之间的最大

怎么由输入数据得到线段树的节点数据,注意改一个数组元素,可能有两个节点信息改变线段树单点修改 



#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 200000;
int a[maxn + 10];
int b[maxn + 10];
struct SegMent {
	int l, r;
	int val;
}que[maxn * 4];
void PushUp(int s) {
	que[s].val = max(que[s << 1].val, que[s << 1 | 1].val);
	return;
}
void buildT(int L, int R, int s = 1) {
	que[s].l = L;
	que[s].r = R;
	if (R == L) {
		que[s].val = a[L];
		return;
	}
	int mid = (L + R) / 2;
	buildT(L, mid, s << 1);
	buildT(mid + 1, R, s << 1 | 1);
	que[s].val = max(que[s << 1].val, que[s << 1 | 1].val);
	//PushUp(s);
	return;
}
void update(int k, int y, int s = 1) {
	if (que[s].l == que[s].r) {
		if (que[s].l == k)
			que[s].val = y;
		return;
	}
	int mid = (que[s].l + que[s].r) / 2;
	if (k <= mid)update(k, y, s << 1);
	else
		update(k, y, s << 1 | 1);
	que[s].val = max(que[s << 1].val, que[s << 1 | 1].val);
	//PushUp(s);
}
int query(int s = 1) {
	if (que[s].l == que[s].r) {
		return que[s].val;
	}
	return max(query(s * 2), query(s * 2 + 1));
}
int main() {
	int n, q;

	while (scanf("%d", &n) != EOF) {
		scanf("%d", &b[1]);
		for (int i = 2; i <= n; i++)
		{
			scanf("%d", &b[i]);
			a[i - 1] = b[i] - b[i - 1];
		}
		scanf("%d", &q);
		buildT(1, n - 1, 1);
		for (int i = 1; i <= q; i++) {
			int p, y;
			scanf("%d%d", &p, &y);
			b[p] = y;
			if (p <= n - 1) {
				a[p] = b[p + 1] - y;
				update(p, a[p], 1);
			}
			if (p > 1) {
				a[p - 1] = y - b[p - 1];
				update(p - 1, a[p - 1], 1);
			}
			printf("%.2lf\n", 1.0 * que[1].val);
		}
	}

	return 0;
}

随机树
 

平日里写hash的时候,总有某些选手由于脸黑而导致惨遭卡模数,然后一些恶意卡模数的出题人也因此身败名裂。为了防止被卡,我们用一种高级的随机方式来代替原来的线性随机生成,也就是所谓的随机树!

现在有一棵编号为0~n-1的有根树,其中0是树的根。每个节点初始有一个值Ti。现在要求支持一下两种操作:

1.  给出两个正整数u和x,我们将Tu的值乘以x,我们将这种操作称为SEED操作。

2.  给出一个正整数i,询问Si以及它一共有多少个正约数。其中Si表示以i为根的子树所有点的权值的乘积,我们将这种操作称为RAND操作。

容易发现,这样得到的答案还是很随机的。(其实不是)

你需要回答每一次的询问,由于一个数的约数个数可能非常多,这个数也可以非常大,你只需要把答案对1e9+7取模就可以了。

输入描述:

第一行一个正整数n,表示节点个数。
接下来n-1行,每行两个正整数u和v,表示u是v的父节点。
接下来一行n个正整数,分别表示每个节点的初始权值Ti。
接下来一行一个正整数q,表示操作的个数。
接下来q行,每行是以下两种情况之一:
1.  SEED u x
表示将u节点的权值乘以x。
2. RAND i
表示询问Si以及它一共有多少个正约数。

输出描述:

每一行两个整数,对应一个RAND操作,你需要输出所求的权值以及它的正约数个数,答案对于1e9+7取模即可。

示例1

输入

8 0 1 0 2 1 3 2 4 2 5 3 6 3 7 7 3 10 8 12 14 40 15 3 RAND 1 SEED 1 13 RAND 1

8
0 1 
0 2 
1 3 
2 4 
2 5 
3 6 
3 7
7 3 10 8 12 14 40 15 
3
RAND 1
SEED 1 13
RAND 1

输出

14400 63 187200 126

14400 63 
187200 126

备注:

 

对于20%的数据,1 ≤ n, q ≤ 10。

对于40%的数据,1 ≤ n, q ≤ 100。

对于60%的数据,1 ≤ n, q ≤ 2000。

对于80%的数据,1 ≤ n, q ≤ 50000。

对于100%的数据,1 ≤ n, q ≤ 100000。

另外请注意,所有读入的数一定满足1 ≤ x ≤ 109 。

同时,数据保证在任意时刻,每个点的权值不可能拥有超过13的素因子,也就是说,每个数的素因子最多只有2,3,5,7,11,13这六种可能。

线段树是处理连续区间的区间和问题

转化,求DFS序列,得出连续区间,再利用线段树求解

参考别人程序改的


#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
typedef long long ll;
using namespace std;

#define ls L,mid,s<<1
#define rs mid+1,R,s<<1|1
const int maxn = 1e5 + 5;
const ll mod = 1e9 + 7;
const ll prime[6] = { 2,3,5,7,11,13 };

struct SegNode {
	int L, R;
	int sum[6];
}que[maxn << 2];

int n, q, cnt;
int in[maxn], out[maxn], value[maxn], factor[6];
vector<int >connect[maxn];
map<int, int> id;
//cnt是连续区间,从1开始
void Dfs(int u) {//从节点u开始深搜
	in[u] = ++cnt;//入口标记
	id[cnt] = u;//??id,根据顺序cnt查出节点u--转化为连续的区间信息
	int size = connect[u].size();//节点u的下一个节点--子节点
	for (int i = 0; i < size; i++)
		Dfs(connect[u][i]); 
	out[u] = cnt;//出口标记
}

ll qpow(int a, int b) {//计算a^b对mod的余
	//ll ans = 0;//????
	ll ans = 1;
	while (b > 0) {
		if (b & 1)ans = ans * a % mod;
		a = (ll)a * a % mod;
		b >>= 1;
	}
	return ans;
}

void build(int L, int R, int s) {
	que[s].L = L;
	que[s].R = R;
	int mid = (que[s].L + que[s].R) / 2;
	memset(que[s].sum, 0, 6 * sizeof(int));
	if (L == R) {//赋值
		for (int i = 0; i < 6; i++) {
			while (value[id[L]] % prime[i] == 0) {
				que[s].sum[i]++;
				value[id[L]] /= prime[i];
			}
		}
		return;
	}
	build(L, mid, s << 1);
	build(mid + 1, R, s << 1 | 1);
	for (int i = 0; i < 6; i++)
		que[s].sum[i] = que[s << 1].sum[i] + que[s << 1 | 1].sum[i];
}

void update(int L, int R, int num,int s) {
	if (L <= que[s].L && R >= que[s].R) {
		for (int i = 0; i < 6; i++) {
			while (num % prime[i] == 0) {
				que[s].sum[i]++;
				num /= prime[i];
			}
		}
		return;
	}
	int mid = (que[s].L + que[s].R) / 2;
	if (L <= mid)update(L, R, num, s << 1);
	if (R > mid)update(L, R, num, s << 1 | 1);
	for (int i = 0; i < 6; i++)
		que[s].sum[i] = que[s << 1].sum[i] + que[s << 1 | 1].sum[i];
	return;
}
void query(int L, int R, int s) {
	if (L <= que[s].L && R >= que[s].R) {
		for (int i = 0; i < 6; i++)
			factor[i] += que[s].sum[i];
		return;
	}
	int mid = (que[s].L + que[s].R) / 2;
	if (L <= mid)query(L, R, s << 1);
	if (R > mid)query(L, R, s << 1 | 1);
	return;
}
int main() {
	scanf("%d", &n);
	//节点关联的下标从1开始
	
	for (int i = 1; i <= n - 1; i++)
	{
		int u, v;
		scanf("%d %d", &u, &v);
		connect[u].push_back(v);
	}
	cnt = 0;
	Dfs(0);
	for (int i = 0; i <= n - 1; i++)scanf("%d", &value[i]);
	build(1, n, 1);//线段树,cnt区间是[1,n] - - 数组下标从1到n,线段树根节点下标是1--id[cnt]对应的值域有0,1,2,3,4,5,6,7---value[i]中的i - in[id]-->cnt
	char mes[5];
	int x;
	ll t;
	scanf("%d", &q);
	for (int k = 1; k <= q; k++) {
		scanf("%s %d", mes, &x);
		if (mes[0] == 'S') {
			scanf("%lld", &t);
			update(in[x], in[x], t, 1);//点修改
		}
		if (mes[0] == 'R') {
			memset(factor, 0, 6 * sizeof(int));
			query(in[x], out[x], 1);
			ll s = 1, ans = 1;
			for (int i = 0; i < 6; i++) {
				s = s * qpow(prime[i], factor[i]) % mod;
				ans = ans * (factor[i] + 1) % mod;//????排列组合
			}
			printf("%lld %lld", s, ans);
		}
		
	}
	return 0;
}

复写一版

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 +7;
const int maxn = 1e5 +10;

int n,q,cnt;
vector<vector<int>> connect(maxn);
//vector<int> connect[maxn];
int in[maxn],out[maxn],value[maxn];
int prime[6] = {2,3,5,7,11,13};
int queryRes[15];
map<int,int>id;
struct SegNode{
    int L,R;
    int sum[6];
}que[maxn<<2];
void Dfs(int u){
    in[u]  = ++cnt;
    id[cnt] = u;
    int size = connect[u].size();
    for(int i = 0;i < size;i++)
        Dfs(connect[u][i]);
    out[u]  = cnt;
}
//a^b mod
ll qpow(int a,int n){
    ll ans = 1;
    while(n>0){
        if(n&1){
            ans = a*ans%mod;
        }
        a = (ll)a*a%mod;
        n>>=1;
    }
    return ans;
}
void build(int L,int R,int s){
    que[s].L = L;
    que[s].R = R;
    memset(que[s].sum,0,6*sizeof(int));
    if(L == R){
        for(int i = 0;i < 6;i++){
            while(value[id[L]]%prime[i] == 0)
            {
                que[s].sum[i]++;
                value[id[L]]/=prime[i];
            }
        }
        return;
    }
    int mid = (L+R)/2;
    build(L,mid,s<<1);
    build(mid+1,R,s<<1|1);
    for(int i = 0;i < 6;i++)
        que[s].sum[i] = que[s<<1].sum[i]+que[s<<1|1].sum[i];
    return;
}

void update(int k,int c,int s){
    if(que[s].L == que[s].R){
        for(int i = 0;i < 6;i++){
            while(c%prime[i] == 0)
            {
                que[s].sum[i]++;
                c /= prime[i];
            }
        }
        return;
    }
    int mid = (que[s].L+que[s].R)/2;
    if(k <= mid)update(k,c,s<<1);
    else update(k,c,s<<1|1);
    for(int i = 0;i < 6;i++)
        que[s].sum[i] = que[s<<1].sum[i]+que[s<<1|1].sum[i];
    return;
}
void query(int L,int R,int s){
    if(que[s].L>=L&&que[s].R<=R){
        for(int i = 0;i < 6;i++)
            queryRes[i]+=que[s].sum[i];
        return;
    }
    int mid = (que[s].L+que[s].R)/2;
    if(mid>=L)query(L,R,s<<1);
    if(mid<R)query(L,R,s<<1|1);
}
int main(){
    scanf("%d",&n);
    for(int i = 1;i <= n-1;i++){//控制输入次数
        int u,v;
        scanf("%d %d",&u,&v);
        connect[u].push_back(v);
    }
    int cnt = 0;
    Dfs(0);
    for(int i = 0;i < n;i++)//i也是节点的标号
        scanf("%d",&value[i]);
    build(1,n,1);
    scanf("%d",&q);
    for(int k = 0;k < q;k++){
        char op[6];
        int u;
        scanf("%s %d",op,&u);
        if(op[0] == 'S'){
            int x;
            scanf("%d",&x);
            update(in[u],x,1);
        }
        if(op[0] == 'R'){
            memset(queryRes,0,sizeof(queryRes));
            query(in[u],out[u],1);
            ll s = 1,ans = 1;
            for(int i = 0;i < 6;i++){
                s =s*qpow(prime[i],queryRes[i])%mod;
                ans = ans*(queryRes[i]+1)%mod;
            }
            printf("%lld %lld\n",s,ans);
        }
    }
    return 0;
}

注意怎么转化,DFS的具体细节

cnt初始值是1,进行Dfs(0),Dfs对应下面的

void Dfs(int u){
    in[u]  = cnt;
    id[cnt] = u;

    cnt++;
    int size = connect[u].size();
    for(int i = 0;i < size;i++)
        Dfs(connect[u][i]);
    out[u]  = cnt;
}

这是错误的

DFS序时间戳

void Dfs(int u){
    in[u]  = ++cnt;
    id[cnt] = u;
    int size = connect[u].size();
    for(int i = 0;i < size;i++)
        Dfs(connect[u][i]);
    out[u]  = cnt;
}

cnt初始值是0;

DFS序,是按照深搜时间顺序的节点编号序列,数组下角标存的是时间。

时间戳,是按照深搜时间顺序的时间编号序列,数组下角标存的是节点编号。

id[i]=x,i是时间,x是节点,id是DFS序。

dfn[x]=i,i是时间,x是节点,dfn是时间戳。

对于树状数组,推荐下面链接树状数组彻底入门 - 半根毛线 - 博客园 (cnblogs.com)https://www.cnblogs.com/hsd-/p/6139376.html 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值