线段树专题

发布时间 2023-10-31 15:44:54作者: superl61

线段树专题

(该笔记持续更新中...)

一、基本操作

1.单点修改/查询:
2.区间修改/查询:

需要用到 lazy_tag 技术,即每次修改不会立刻修改涉及到的每一段区间,而是等到下一次修改要用到或者是要查询该区间时再更新,这样可以将每次修改和查询的复杂度控制在 \(O(log_2N)\)

3.总结:

1)修改时记得都要 pushup

2)在向下走的过程中,对于单点操作都是“非左即右”的,而区间操作一定是同时考虑

以修改为例:

//单点
if(x<=mid) upd(ls,x,y);
else upd(rs,x,y);
//区间
mid=tr[p].l+tr[p].r>>1
if(x<=mid) upd(ls,x,y,z);
if(y>mid) upd(rs,x,y,z);

二、应用:

  1. 向序列中动态插入元素

    维护一个队列,初始为空。依次加入 \(n \ (1≤n≤3 \times 10^5)\) 个数 \(a_i \ (-10^9≤a_i≤10^9)\),第 $i \ (1≤i≤n) $个数加入到当前序列第 \(b_i \ (0≤b_i≤\)当前序列长度\()\)后面。输出最终队列。

    首先一定审清楚题,放的位置是当前序列第bi个数后面,不是数组中下标为bi的那个位置,也就是说操作过程中序列一直是连续的一段。

    考虑倒着执行处理操作,因为先放的数会被后方的数挤到后面去,所以数的位置越靠后变动越小

    在线段树上找到当前数应该放的位置即可。

    #include<bits/stdc++.h>
    using namespace std;
    char buf[100],*p1=buf,*p2=buf;
    inline int gc(){ return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,100,stdin),p1==p2)?EOF:*p1++; }
    inline int rd(){
    	int x=0; char ch=gc(); bool f=1;
    	while(!isdigit(ch)){
    		if(ch=='-') f=0;
    		ch=gc();
    	}
    	while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=gc();
    	return f?x:-x;
    }
    const int N=3e5+5;
    struct node{
    	int a,b;
    }q[N];
    int n,tr[N<<2],ans[N];
    inline void modify(int p,int l,int r,int x,int y){
    	if(l==r) return ans[l]=y,tr[p]=1,void();
    	int mid=l+r>>1,kkk=mid-l+1-tr[p<<1];
    //	printf("%d %d %d %d %d\n",l,r,x,y,kkk);
    	if(x<=kkk) modify(p<<1,l,mid,x,y);
    	else modify(p<<1|1,mid+1,r,x-kkk,y);
    	tr[p]=tr[p<<1]+tr[p<<1|1];
    	return ;
    }
    int main(){
    //	freopen("tr.in","r",stdin);
    	n=rd(); for(int i=1;i<=n;++i) q[i].a=rd(),q[i].b=rd();
    	for(int i=n;i>=1;--i) modify(1,1,n,q[i].b+1,q[i].a);
    	for(int i=1;i<=n;++i) printf("%d\n",ans[i]);
    	return 0;
    }
    

2.全局区间并

维护一个多重集S, 初始为空,\(n\) 次操作,共三种:

1.把 \([l,r]\) 加入 S。(op=0)

2.删除S中的一个 \([l,r]\) ,保证删除的元素一定在 S 中出现。(op=1)

3.询问当前 S 中所有区间并的长度。(op=2)

\(1 \leq n \leq 10^5,-10^9 \leq l<r \leq 10^9\)

又是文字游戏!审清题!审清题!审清题!

保证删除的元素一定在 S 中出现。

细品“删除的元素”,恍然大悟是把一段区间以一个元素的方式加入集合。

这说明什么呢?

说明其实删除和修改都是单点的,不是区间的!!!

所以其实此题不是完全的维护区间并。

and根本不需要懒标记! 直接找到对应区间,修改,然后pushup即可。(还有一些细节在代码里交代)

#include<bits/stdc++.h>
using std::cin;
using std::cout;
using std::min;
#define f(x) std::lower_bound(rk+1,rk+cnt+1,x)-rk
const int N=2e5+5;
int sum[N<<2],len[N<<2],rk[N],cnt=0,n;
int op[N],ql[N],qr[N];
inline void upd(int p,int l,int r){
	if(sum[p]>0) len[p]=rk[r+1]-rk[l];
	//比如你加入了一个区间[1,3]和一个[2,3],现在你想删[2,3],直接删掉就行,[1,3]仍然存在 
	//根本不影响[1,3]的sum[p],所以当对[1,3]pushup时仍然用sum[p]更新len[p]是正确的 
	else len[p]=l==r?0:len[p<<1]+len[p<<1|1];
	//else有两种情况会用到
	//1.删的区间刚好就是p
	//2.没有对p进行任何操作,但在p的子树内有各种操作。现在删的区间是p的儿子,pushup(p)时用到 
}
inline void modify(int p,int l,int r,int x,int y,int ctrl){
	if(l>=x && r<=y){
		sum[p]+=ctrl?-1:1;
		upd(p,l,r);
		return ;
	} int mid=l+r>>1;
	if(mid>=x) modify(p<<1,l,mid,x,y,ctrl);
	if(mid<y) modify(p<<1|1,mid+1,r,x,y,ctrl);
	upd(p,l,r);//上传答案 
	return ;
}
int main(){
	std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin>>n; 
	for(int i=1;i<=n;++i){
		cin>>op[i]; if(op[i]!=2){
			cin>>ql[i]>>qr[i];
			rk[++cnt]=ql[i],rk[++cnt]=qr[i];
		}
	}
	std::sort(rk+1,rk+cnt+1); cnt=std::unique(rk+1,rk+cnt+1)-rk-1;
	for(int i=1;i<=n;++i){
		if(op[i]==2) cout<<len[1]<<'\n';
		else modify(1,1,cnt-1,f(ql[i]),f(qr[i])-1,op[i]);	
	}
	return 0;
}