线段树专题
线段树专题
(该笔记持续更新中...)
一、基本操作
1.单点修改/查询:
2.区间修改/查询:
需要用到 lazy_tag 技术,即每次修改不会立刻修改涉及到的每一段区间,而是等到下一次修改要用到或者是要查询该区间时再更新,这样可以将每次修改和查询的复杂度控制在
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);
二、应用:
-
向序列中动态插入元素
维护一个序列,初始为空。依次加入
个数 ,第 个数加入到当前序列第 当前序列长度 后面。输出最终序列。首先一定审清楚题,放的位置是当前序列第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, 初始为空,
1.把
2.删除S中的一个
3.询问当前 S 中所有区间并的长度。(op=2)
又是文字游戏!审清题!审清题!审清题!
保证删除的元素一定在 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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现