首页 > 其他分享 >「Day 7—离散化 & 树状数组 & 线段树」

「Day 7—离散化 & 树状数组 & 线段树」

时间:2024-08-12 22:27:11浏览次数:10  
标签:树状 int sum mid Day add rc 区间 线段

离散化

定义

离散化本质是一种哈希,是一种用来处理数据的方法。
1.创建原数组的副本。
2.将副本中的值从小到大排序。
3.将排序好的副本去重。
4.查找原数组的每一个元素在副本中的位置,位置即为排名,将其作为离散化后的值。

B3694 数列离散化

代码

#include<iostream>
#include<algorithm>
using namespace std;

const int MAXN = 1e5 + 5;
int n;
int T; 
int a[MAXN],tmp[MAXN];

void lsh(){
	for(int i = 1;i <= n;i ++){
		//复制一个数组用来去重用
		tmp[i] = a[i];
	}
	sort(tmp + 1,tmp + n + 1);
	//去重
	int len = unique(tmp + 1,tmp + n + 1) - tmp - 1;
	for(int i = 1;i <= n;i ++){
		//离散
		a[i] = lower_bound(tmp + 1,tmp + len + 1,a[i]) - tmp;
	}
	for(int i = 1;i <= n;i ++){
		cout << a[i] << " ";
	}
	cout << "\n";
}

int main(){
	cin >> T;
	while(T --){
		cin >> n;
		for(int i = 1;i <= n;i ++){
			cin >> a[i];
		}
		lsh();
	}
	return 0;
}

树状数组

定义

树状数组是一种支持 单点修改区间查询 的数据结构。
下图是一个树状数组的示意图,

具体的看代码

P3374 【模板】树状数组 1

代码

#include<iostream>
using namespace std;

const int MAXN = 5 * 1e5 + 5;
int n,m;
int sum[MAXN];

int lowbit(int x) {
  // x 的二进制中,最低位的 1 以及后面所有 0 组成的数。
  // lowbit(0b01011000) == 0b00001000
  //          ~~~~^~~~
  // lowbit(0b01110010) == 0b00000010
  //          ~~~~~~^~
  return x & -x;
}
//将x加上k
void add(int x,int k){
	while(x <= n) sum[x] += k,x += lowbit(x);
}

//计算从1 ~ x的和
int f(int x){
	int ans = 0;
	while(x) ans += sum[x],x -= lowbit(x);
	return ans;
}

int main(){
	cin >> n >> m;
	for(int i=1;i<=n;i++){
		int x;
		cin >> x;
		add(i,x);
	}
	for(int i = 1;i <= m;i ++){
		int op,x,y;
		cin >> op >> x >> y;
		if(op == 1) add(x,y);
		else cout << f(y) - f(x - 1) << "\n";
	}
}

P3368 【模板】树状数组 2

代码

#include<iostream>
using namespace std;

const int MAXN = 5 * 1e5 + 5;
int n,m;
int sum[MAXN],a[MAXN];

int lowbit(int x){
	return x&-x;
}
void add(int x,int k){
	while(x <= n) sum[x] += k,x += lowbit(x);
}
int f(int x){
	int ans = 0;
	while(x) ans += sum[x],x -= lowbit(x);
	return ans;
}

int main(){
	cin >> n >> m;
	for(int i=1;i<=n;i++){
		cin >> a[i];
	}
	for(int i = 1;i <= m;i ++){
		int op,x,y,k;
		cin >> op >> x;
		if(op == 1){
			cin >> y >> k;
			//运用差分的思想 差分数组在x处+k,在y+1处-k就完成了对x~y的区间加k问题
			add(x,k);
			add(y + 1,- k);
		}
		else cout << a[x] + f(x) << "\n";
	}
}

线段树

定义

线段树是为了维护区间信息的一种数据结构,线段树可以在 \(O(\log N)\) 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。

P3372 【模板】线段树 1

思路

我们用递归建树来完成,具体的看代码。

代码

定义结构体
struct node{
//l是左子树的节点编号,r是右子树的节点编号,sum为当前节点的区间和(l~r),add是当前节点的懒惰标记。
	int l,r,sum,add;
}t[MAXN * 4]; 
上传操作
void pushup(int p){
//当前节点的区间和=左子树的区间和+右子树的区间和
	t[p].sum = t[lc].sum + t[rc].sum;
	return;
}
下传操作
void pushdown(int p){
//判断当前节点是否有懒惰标记,如果有就下放给其左右子树
	if(t[p].add){
		//下放左子树
		t[lc].add += t[p].add;
		//下放右子树
		t[rc].add += t[p].add;
		//同时也要将懒惰标记的数值下放给左右子树的区间值
		//左子树的区间+区间元素数量*懒惰标记
		t[lc].sum += t[p].add * (t[lc].r - t[lc].l + 1);
		//右子树的区间+区间元素数量*懒惰标记
		t[rc].sum += t[p].add * (t[rc].r - t[rc].l + 1);
		//下放完成后将根节点的懒惰标记清0
		t[p].add = 0;
	}
}
建树
void build(int p,int l,int r){
	//我们采用递归建树的方式
 	t[p] = {l,r,0,0};
	//l = r说明这是一个叶子结点,则区间值也就是(l~r)的值为其本身的值x[l]或x[r],这个无所谓
 	if(l == r){
 		t[p].sum = x[l];
 		return;
 	}
	//位运算避免溢出,等同于(l + r) / 2 或者 (l + r) >> 1
	int mid = (l & r) + ((l ^ r) >> 1);
	//以lc(p << 1)为左子树的根建立以l为区间左端点,mid为区间右端点的左子树
	build(lc,l,mid);
	//以rc (p << 1 | 1) 为右子树的根建立以mid+1为区间左端点,r为区间右端点的右子树
	build(rc,mid + 1,r);
	//进行上传操作,上传的原因是因为既然左子树和右子树都建立了,且其区间值也计算完毕,那么就应该利用左右子树向上传递其区间值。
	//例如p为【1~2】的区间,左子树lc【1~1】的值为4,右子树rc【2~2】的值为6.
	//那么p的值此时为lc.sum + rc.sum,即4 + 6 = 10,。
	pushup(p);
}
区间查询
int query(int p,int l,int r){
	//如果当前节点 p 代表的区间 [t[p].l, t[p].r] 完全在我们查询的区间 [l, r] 之内
	//(即 l <= t[p].l 且 t[p].r <= r),那么我们可以直接返回这个节点的和 t[p].sum,而不需要进一步递归地查询子节点。
	if(l <= t[p].l && t[p].r <= r) return t[p].sum;
	//依旧是位运算,求的是当前节点p的区间中点
	int mid = (t[p].l & t[p].r) + ((t[p].l ^ t[p].r) >> 1);
	//下传一下懒惰标记,防止没有下传导致查询出错
	pushdown(p);
	int sum = 0;
	//如果要查的区间的左端点比mid小,说明还要向左子树继续递归求值
	if(l <= mid){
		sum += query(lc,l,r);
	}
	//如果要查的区间的右端点比mid大,说明还要向右子树继续递归求值
	if(r > mid){
		sum += query(rc,l,r);
	}
	//最后返回左右子树的值之和
	return sum;
}
区间修改
void change(int p,int l,int r,int k){
	//如果区间刚好对上了,那么就让当前的节点的区间值+区间元素*k
	if(l <= t[p].l && t[p].r <= r){
		t[p].sum += (t[p].r - t[p].l + 1) * k;
		//懒惰标记,方便下传
		t[p].add += k;
		return;
	}
	//如果没对上说明还是有左右子树需要继续修改
	//又双叒是位运算,求的是当前节点p的区间中点
	int mid = (t[p].l & t[p].r) + ((t[p].l ^ t[p].r) >> 1);
	//下传懒惰标记,防止在修改子树是区间值不对
	pushdown(p);
	//如果要修改的区间的左端点比mid小,说明还要向左子树继续递归修改
	if(l <= mid) change(lc,l,r,k);
	//如果要修改的区间的右端点比mid大,说明还要向右子树继续递归修改
	if(r > mid) change(rc,l,r,k);
	//修改完别忘了去给p更新值
	pushup(p);
}

完整代码

#include<iostream>
#define lc p << 1
#define rc p << 1 | 1
#define int long long
using namespace std;

const int MAXN = 1e5 + 5;
int n,m,x[MAXN];
struct node{
	int l,r,sum,add;
}t[MAXN * 4]; 
//上传 
void pushup(int p){
	t[p].sum = t[lc].sum + t[rc].sum;
	return;
}
//下传 
void pushdown(int p){
	if(t[p].add){
		t[lc].add += t[p].add;
		t[rc].add += t[p].add;
		t[lc].sum += t[p].add * (t[lc].r - t[lc].l + 1);
		t[rc].sum += t[p].add * (t[rc].r - t[rc].l + 1);
		t[p].add = 0;
	}
}
//建树 
void build(int p,int l,int r){
// 	t[p] = {l,r,x[l],0};
 	t[p] = {l,r,0,0};
 	if(l == r){
 		t[p].sum = x[r];
 		return;
 	}
	int mid = (l & r) + ((l ^ r) >> 1);
	build(lc,l,mid);
	build(rc,mid + 1,r);
	pushup(p);
}
int query(int p,int l,int r){
	if(l <= t[p].l && t[p].r <= r) return t[p].sum;
	int mid = (t[p].l & t[p].r) + ((t[p].l ^ t[p].r) >> 1);
// 	int mid = (t[p].l + t[p].r) / 2;
	pushdown(p);
	int sum = 0;
	if(l <= mid){
		sum += query(lc,l,r);
	}
	if(r > mid){
		sum += query(rc,l,r);
	}
	return sum;
}
void change(int p,int l,int r,int k){
	if(l <= t[p].l && t[p].r <= r){
		t[p].sum += (t[p].r - t[p].l + 1) * k;
		t[p].add += k;
		return;
	}
	int mid = (t[p].l & t[p].r) + ((t[p].l ^ t[p].r) >> 1);
	pushdown(p);
	if(l <= mid) change(lc,l,r,k);
	if(r > mid) change(rc,l,r,k);
	pushup(p);
}

signed main(){
	cin >> n >> m;
	for(int i = 1;i <= n;i ++){
		cin >> x[i];
	}
	build(1,1,n);
	for(int i = 1;i <= m;i ++){
		int op,x,y,k;
		cin >> op >> x >> y;
		if(op == 1){
			cin >> k;
			change(1,x,y,k); 
		}
		else{
			cout << query(1,x,y) << "\n";
		}
	} 
	return 0;
}

标签:树状,int,sum,mid,Day,add,rc,区间,线段
From: https://www.cnblogs.com/To-Carpe-Diem/p/18354001

相关文章

  • 青甘环线游记|day(9~10)|祁连山大草原、黄河
    祁连山大草原祁连山的平均山脉海拔在4000米~5000米之间,高山积雪形成的硕长而宽阔的冰川地貌奇丽壮观。海拔高度在4000米以上的地方,称为雪线,祁连山的雪线之上,常常会出现逆反的生物奇观。在浅雪的山层之中,有名为雪山草甸植物的蘑菇状蚕缀,还有珍贵的药材——高山雪莲,以及一种......
  • 8.12 Day5
    推荐歌曲《我是逆蝶》。ADivideSquare挖掘特殊点:有一个端点在边缘上。如果我们扫x坐标,维护lst横和交叉的竖,非常不好维护,并且TLE。结论:一个交点会至少增加一个区域。证明显然。当然还有一点cornercase。BCowTennisTournament一开始想的是三元环会是怎的,推出的......
  • 代码随想录Day12
    二叉树遍历分为前序、中序、后续、层序四种其中前中后序属于深度优先搜索,层序属于广度优先搜索前序遍历顺序:根节点->左子树->右子树中序遍历顺序:左子树->根节点->右子树后序遍历顺序:左子树->右子树->根节点不难发现,前中后其实就是根节点在遍历中的位置至于层序遍历,顾名......
  • 区间历史最值线段树记录
    Description维护一个线段树,使得可以实现区间加、区间chkmin、求区间最值、区间历史最值、区间最大值。Solution先不考虑区间chkmin和历史最值,可以直接对于每个线段树节点维护一个tag,每次addtag更新。加上区间历史最值后,先考虑对于单个线段树节点怎么更新。容易发现对于......
  • LinkedHashSet day14
    /*LinkedHashSet是继承自HashSet类,底层数据结构是哈希表和双链表,哈希表保证了元素的唯一性,双链表保证了元素的有序Collection:接口-List(元素有序且可以发生重复,且有索引的概念)-ArrayList(底层数据结构是数组,查询快,增删慢,线程......
  • 【单调栈+倍增】[P7167 [eJOI2020 Day1] Fountain
    【单调栈+倍增】[P7167[eJOI2020Day1]Fountain思路用单调栈处理每个圆盘溢出后流到的第一个位置,然后倍增优化。代码#include<bits/stdc++.h>usingnamespacestd;usingi64=longlong;intmain(){ios::sync_with_stdio(false);cin.tie(nullptr);......
  • 线段树水题集合
    用的都是《算法竞赛》的码风哦洛谷P3870极简,开和关用10表示,然后区间修改,区间求和,裸题洛谷P2846这两道题一模一样#include<bits/stdc++.h>usingnamespacestd;constintN=100005;#definelllonglongllls(llp){returnp<<1;}llrs(llp){returnp<<1|1;}lln......
  • 鸿蒙-JS-第二周day01
    数组1什么是数组1)数组是值的有序集合。2)每个值叫做一个元素。3)每个元素在数组中有一个位置,以数字表示,称为索引(有时也称为下标)。4)数组的元素可以是任何类型。5)数组索引从0开始,数组最大能容纳4294967295个元素。2创建数组2.1使用数组直接量//......
  • 实习记录day01
    实习第一天上午:没想到提示的走路1.6公里这么远,差点迟到,公司离地铁站好远,下次要骑车过来,想不到这次居然把我腿走断了,一上午还没有恢复过来。(现在下午了,也没恢复过来)这个地方的电梯真离谱,居然是两面开的,我嗯了半天还以为这个电梯坏了,真绝了。配置了公司内网的相关软件,为了链接内......
  • 日撸Java三百行(day20:小结)
    目录前言一、面向对象和面向过程相比,有哪些优势?1.封装2.继承3.多态4.协作5.组织结构二、比较顺序表和链表的异同1.相同点2.不同点2.1物理存储结构2.2查找2.3插入和删除三、分析顺序表和链表的优缺点1.顺序表1.1顺序表的优点1.2顺序表的缺点2.链表2.1链表的......