美妙的神奇的线段树

线段树

板子

注意要用结构体

如:当维护信息很多或有绝对值,这样很好写(下面是含区间绝对值代码)注释的为区间加

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define lson x<<1
#define rson x<<1|1
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct node{//结构体线段树只改四(五)个地方
	int sum;//代表区间相邻两数差的绝对值的和
	int lv;//最左边的数是什么
	int rv;//最右边的数是什么//维护的值是第一个改动
	int size;
	int add;
	node(){
		sum=size=add=0;
	}
	void chu(int v){
//		sum=v;
//		size=1;区间加
		lv=rv=v;//初始化第二改动
		sum=0;
	}
}tr[400005];
int a[100005],n,q;
node operator+(const node &l,const node &r){
	node ans;
//	ans.sum=l.sum+r.sum;
//	ans.size=l.size+r.size;
	ans.sum=l.sum+r.sum+abs(l.rv-r.lv);//重载运算符第三改动
	ans.lv=l.lv;
	ans.rv=r.rv;
	return ans;
}
void color(int l,int r,int x,int v){
	tr[x].add+=v;
//	tr[x].sum+=tr[x].size*v;//区间操作第四改
	tr[x].lv+=v;
	tr[x].rv+=v;
}
void pushdown(int l,int r,int x){
	int mid=(l+r)>>1;
	color(l,mid,lson,tr[x].add);
	color(mid+1,r,rson,tr[x].add);
	tr[x].add=0;
}
void build(int l,int r,int x){
	if(l==r){
		tr[x].chu(a[l]);
		return ;
	}
	int mid=(l+r)>>1;
	build(l,mid,lson);
	build(mid+1,r,rson);
	tr[x]=tr[lson]+tr[rson];
}
node query(int l,int r,int x,int nl,int nr){
	if(nl<=l&&r<=nr) return tr[x];
	pushdown(l,r,x);
	int mid=(l+r)>>1;
	if(nl<=mid){
		if(mid<nr) return query(l,mid,lson,nl,nr)+query(mid+1,r,rson,nl,nr);
		else return query(l,mid,lson,nl,nr);
	}
	else return query(mid+1,r,rson,nl,nr);
}
void modify(int l,int r,int x,int nl,int nr,int v){
	if(nl<=l&&r<=nr){
		color(l,r,x,v);
		return ;
	}
	pushdown(l,r,x);
	int mid=(l+r)>>1;
	if(nl<=mid) modify(l,mid,lson,nl,nr,v);
	if(mid<nr) modify(mid+1,r,rson,nl,nr,v);
	tr[x]=tr[lson]+tr[rson];
}
signed main()
{
	//freopen("filename.in", "r", stdin);
	//freopen("filename.out", "w", stdout);
	n=read(),q=read();
	for(int i=1;i<=n;i++) a[i]=read();
	build(1,n,1);
	while(q--){
		int opt,x,y,k;
		cin>>opt>>x>>y;
		if(opt==1){
			cin>>k;
			modify(1,n,1,x,y,k);
		}
		else cout<<query(1,n,1,x,y).sum<<'\n';
	}
	return 0;
}

例题:

1.区间平方和

把平方拆开

2.线段树2 乘+加

先乘后加(如有推平放最前面)

add,mul写一个函数

3.区间等差数列

tag记录x,y(首项末项)

标记下放时,发现合并后仍然为等差数列

x,y直接相加,sum=sum+(x+x+(siz-1))ysiz/2

对于左右儿子

左儿子x,y直接操作

右儿子y不变,x为x+siz[ls]*y

操作即可

4.逆序对

给定N个数,M次询问,每次给定i,要求把i往后的所有数中<=a[i]的数拿出来排序,放回原数组

每次询问求操作后的数组逆序对的个数

M,N,a[i]<=1e5

首先,如果按照题目模拟排序,时间复杂度O(mnlogn),炸

所以考虑直接求逆序对个数

发现排序后,逆序对个数只少不多

考虑记录一个f[i]表示i后与多少个数比a[i]小

f[i]的和即为逆序对个数

发现每次操作拿出的数的f[i]都会变成0

所以新的逆序对即为原有答案将拿出的数的f[i]的和减掉即可

而这个暴力也会炸

所以考虑线段树

记录sum,minn

每次查询i~n的minn,若minn<=a[i],说明区间有值小于a[i],递归

找到一个节点,f[j]=0,a[j]=正无穷(防止重复计数,确保复杂度)

因为每个点只访问一次

时间复杂度O(nlogn)

5.区间AND区间除区间求和

保镖

但本题卡常

完整做法如下

6.Friends

7.Blog Post Rating

可持久化数据结构

可持久化并查集

支持撤销

但不可用路径压缩

板子可持久化并查集

可持久化线段树

tag不下传

板子【模板】可持久化线段树 1(可持久化数组)

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define lson x<<1
#define rson x<<1|1
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct node{
	int sum;
	int l,r;
	node(){
		sum=l=r=0;
	}
}tr[20000005];//大小为maxn*logn
int cnt;//时间戳
void update(int x){
	tr[x].sum=tr[tr[x].l].sum+tr[tr[x].r].sum;
}
int a[1000005],n,q,rt[1000005];
int build(int l,int r){//当前区间l~r,返回这段区间对应的节点编号(时间戳)
	cnt++;
	int x=cnt;
	if(l==r){
		tr[x].sum=a[l];
		return x;
	}
	int mid=(l+r)>>1;
	tr[x].l=build(l,mid);
	tr[x].r=build(mid+1,r);
	update(x);
	return x;
}
int query(int l,int r,int x,int nl,int nr){//询问代码几乎不变(区间加)
	if(nl<=l&&r<=nr) return tr[x].sum;
	int mid=(l+r)>>1;
	if(nl<=mid){
		if(mid<nr) return query(l,mid,tr[x].l,nl,nr)+query(mid+1,r,tr[x].r,nl,nr);
		else return query(l,mid,tr[x].l,nl,nr);
	}
	else return query(mid+1,r,tr[x].r,nl,nr);
}
int modify(int l,int r,int x,int p,int v){//单点加v,新建一颗线段树修改,返回修改后新节点编号(时间戳)
	cnt++;
	int pq=cnt;
	tr[pq]=tr[x];
	if(l==r){
		tr[pq].sum+=v;
		return pq;
	}
	int mid=(l+r)>>1;
	if(p<=mid) tr[pq].l=modify(l,mid,tr[pq].l,p,v);
	else tr[pq].l=modify(mid+1,r,tr[pq].r,p,v);
	update(pq);
	return pq;
}
signed main()
{
	//freopen("filename.in", "r", stdin);
	//freopen("filename.out", "w", stdout);
	n=read(),q=read();
	for(int i=1;i<=n;i++) a[i]=read();
	rt[0]=build(1,n);//rt[i]表示第i次操作后根节点是谁
	for(int i=1;i<=q;i++){
		int k=read(),opt=read();
		if(opt==1){
			int p=read(),v=read();
			rt[i]=modify(1,n,rt[k],p,v);
		}
		else{
			int l=read();
			//int r=read();
			//cout<<query(1,n,rt[k],l,r)<<'\n';//区间加
			cout<<query(1,n,rt[k],l,l)<<'\n';
			rt[i]=rt[k];
		} 
	}
	return 0;
}

平衡树(两者会一个即可)

Spaly

旋转

Treap

分裂合并

https://www.luogu.com.cn/problem/P3215

平衡树板子

https://www.luogu.com.cn/problem/P7739

https://www.luogu.com.cn/problem/P5066

posted @ 2025-05-13 22:32  gbrrain  阅读(4)  评论(0)    收藏  举报