二维线段树
关于二维的线段树,好像是有两种方法实现?
一种为每个一维节点做一个线段数,就是树套树,另一种是四叉的一维线段树。本人实属菜鸡,只记录了第一中方法。
第一种方法时间复杂度是log^2,空间复杂度是n*n*log^2,似乎也够用哈😀。
基本思想:
把第二维(即矩阵的y轴)看作一维(x轴)的一个节点,而这个节点又是一个单独的线段树。
如图是一个4*4的矩阵,我们可以现在x轴建立一个线段树,然后再在每一个x轴的节点下建立关于y轴的线段树。这样不管是查询还是修改,只需要先找到x轴的线段树节点,再遍历y轴的线段树即可。
具体操作
1.建树&单点修改
x轴方向:
void upd1(int l,int r,int o,int x,int y,int v){//x为要更新的一维下标,y为要更新的二维下标
if(l==r){
fg=1;//打上标记方便二维线段树确定一维节点
xo=o;
upd2(1,n,1,y,v);
return ;
}
int mid=l+((r-l)>>1);
if(x<=mid)
upd1(l,mid,o<<1,x,y,v);
else
upd1(mid+1,r,o<<1|1,x,y,v);
fg=0,xo=o;
upd2(1,n,1,y,v);
}
y轴方向:
o为二维节点,xo为一维节点,v即想要赋予的值
void upd2(int l,int r,int o,int y,int v){
if(l==r){//如果找到了二维下标
if(fg)//如果此时正好找到了一维下标,就赋值
mi[xo][o]=v,mx[xo][o]=v;
else{//否则更新一维的情况
mi[xo][o]=min(mi[xo<<1][o],mi[xo<<1|1][o]);
mx[xo][o]=max(mx[xo<<1][o],mx[xo<<1|1][o]);
}
return ;
}
int mid=l+((r-l)>>1);
if(y<=mid)
upd2(l,mid,o<<1,y,v);
else
upd2(mid+1,r,o<<1|1,y,v);
mi[xo][o]=min(mi[xo][o<<1],mi[xo][o<<1|1]);//维护二维线段树
mx[xo][o]=max(mx[xo][o<<1],mx[xo][o<<1|1]);
}
可以发现,相对于一维的线段树,二维线段树不需要重新开一个数组来存初始值(一维的也可以不用)分开来看就是两个基本的一维线段树,只不过相互连接起来罢了。
建树和修改完全相同,每个位置附上想要的值即可。
2.查询
同样,查询操作也是首先找到x轴的节点,然后深入二维线段树确定所查询的区间。
mians=inf,mxans=0;
mians为最小值,mxnas为最大值。
查询时查询的为左上角x,y到右下角x1,y1的范围矩阵。
x轴:
void qy1(int l,int r,int o,int lx,int rx,int ly,int ry){
if(lx<=l&&rx>=r){
qy2(1,n,1,o,ly,ry);
return ;
}
int mid=l+((r-l)>>1);
if(lx<=mid)
qy1(l,mid,o<<1,lx,rx,ly,ry);
if(rx>mid)
qy1(mid+1,r,o<<1|1,lx,rx,ly,ry);
}
y轴:
void qy2(int l,int r,int o,int pre,int ly,int ry){
if(ly<=l&&ry>=r){
mians=min(mians,mi[pre][o]);
mxans=max(mxans,mx[pre][o]);
return ;
}
int mid=l+((r-l)>>1);
if(ly<=mid)
qy2(l,mid,o<<1,pre,ly,ry);
if(ry>mid)
qy2(mid+1,r,o<<1|1,pre,ly,ry);
}
查询时因为所需范围确定,在y轴线段树中无需更新一维的情况,区间和也同理。
最后附上完整模板:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1500;
const int inf=1e8;
int n,q,mx[maxn][maxn],mi[maxn][maxn];
int fg,xo,mians,mxans;
void upd2(int l,int r,int o,int y,int v){
if(l==r){//如果找到了二维下标
if(fg)//如果此时正好找到了一维下标,就赋值
mi[xo][o]=v,mx[xo][o]=v;
else{//否则更新一维的情况
mi[xo][o]=min(mi[xo<<1][o],mi[xo<<1|1][o]);
mx[xo][o]=max(mx[xo<<1][o],mx[xo<<1|1][o]);
}
return ;
}
int mid=l+((r-l)>>1);
if(y<=mid)
upd2(l,mid,o<<1,y,v);
else
upd2(mid+1,r,o<<1|1,y,v);
mi[xo][o]=min(mi[xo][o<<1],mi[xo][o<<1|1]);//维护二维线段树
mx[xo][o]=max(mx[xo][o<<1],mx[xo][o<<1|1]);
}
void upd1(int l,int r,int o,int x,int y,int v){//x为要更新的一维下标,y为要更新的二维下标
if(l==r){
fg=1;//打上标记方便二维线段树确定一维节点
xo=o;
upd2(1,n,1,y,v);
return ;
}
int mid=l+((r-l)>>1);
if(x<=mid)
upd1(l,mid,o<<1,x,y,v);
else
upd1(mid+1,r,o<<1|1,x,y,v);
fg=0,xo=o;
upd2(1,n,1,y,v);
}
void qy2(int l,int r,int o,int pre,int ly,int ry){
if(ly<=l&&ry>=r){
mians=min(mians,mi[pre][o]);
mxans=max(mxans,mx[pre][o]);
return ;
}
int mid=l+((r-l)>>1);
if(ly<=mid)
qy2(l,mid,o<<1,pre,ly,ry);
if(ry>mid)
qy2(mid+1,r,o<<1|1,pre,ly,ry);
}
void qy1(int l,int r,int o,int lx,int rx,int ly,int ry){
if(lx<=l&&rx>=r){
qy2(1,n,1,o,ly,ry);
return ;
}
int mid=l+((r-l)>>1);
if(lx<=mid)
qy1(l,mid,o<<1,lx,rx,ly,ry);
if(rx>mid)
qy1(mid+1,r,o<<1|1,lx,rx,ly,ry);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
int v;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>v;
upd1(1,n,1,i,j,v);
}
}
cin>>q;
while(q--){
string op;
cin>>op;
int x,y,x1,y1;
if(op[0]=='q'){
cin>>x>>y>>x1>>y1;
mians=inf;
mxans=0;
qy1(1,n,1,x,x1,y,y1);
cout<<mians<<' '<<mxans<<'\n';
}
else{
cin>>x>>y>>v;
upd1(1,n,1,x,y,v);
}
}
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析