给定一个N*N的矩阵,每个格子都有一个数,再给出Q个询问,每次询问以(x,y)为中心的边长为L的正方形矩阵中的最大值和最小值,并修改(x,y)的值为(MAX+MIN)/2
1)四叉树
对于查询矩阵的最值+修改问题,考虑二维线段树,参照一维线段树的写法:每次将区间二分成两个子区间,对应矩阵应对X,Y同时二分,也就是4个子矩阵,即左上、右上、左下,右下四部分,所以要建一颗四叉树,如果当前矩阵为一维时,只需二分成两个子区间,如下图所示:
归纳总结得出节点X的儿子节点为:X*4-2+a(0=<a<=3,根节点为1),其他就和一维线段树一样了
#include
using namespace std;
const int maxn = 1e3+13;
int n,m,x,y,L,T,q,a[maxn][maxn];
struct tree{
int x1,y1,x2,y2;
int MAX,MIN;
}tr[maxn*maxn*4];
inline int son(int p,int x){
return p*4-2+x;
}
void pushup1(int x){ //四叉树的pushup
tr[x].MAX = max(tr[son(x,0)].MAX,tr[son(x,1)].MAX);
tr[x].MIN = min(tr[son(x,0)].MIN,tr[son(x,1)].MIN);
for(int i = 2;i < 4; ++i){
tr[x].MAX = max(tr[x].MAX,tr[son(x,i)].MAX);
tr[x].MIN = min(tr[x].MIN,tr[son(x,i)].MIN);
}
}
void pushup2(int x){ //二叉树的pushup
tr[x].MAX = max(tr[son(x,0)].MAX,tr[son(x,1)].MAX);
tr[x].MIN = min(tr[son(x,0)].MIN,tr[son(x,1)].MIN);
}
void build(int X1,int Y1,int X2,int Y2,int index){
tr[index].x1 = X1,tr[index].x2 = X2;
tr[index].y1 = Y1,tr[index].y2 = Y2;
if(X1 == X2 && Y1 == Y2){
tr[index].MAX = a[X1][Y1];
tr[index].MIN = a[X1][Y1];
return ;
}
int midx = (X1+X2) >> 1;
int midy = (Y1+Y2) >> 1;
if(X1 == X2){
build(X1,Y1,X2,midy,son(index,0));
build(X1,midy+1,X2,Y2,son(index,1));
pushup2(index);
}
else if(Y1 == Y2){
build(X1,Y1,midx,Y2,son(index,0));
build(midx+1,Y1,X2,Y2,son(index,1));
pushup2(index);
}
else{ //四个子矩阵
build(X1,Y1,midx,midy,son(index,0));
build(midx+1,Y1,X2,midy,son(index,1));
build(X1,midy+1,midx,Y2,son(index,2));
build(midx+1,midy+1,X2,Y2,son(index,3));
pushup1(index);
}
}
int A,B;
void query(int X1,int Y1,int X2,int Y2,int x){
if(tr[x].x1>X2||tr[x].x2Y2||tr[x].y2=X1&&tr[x].x2<=X2&&tr[x].y1>=Y1&&tr[x].y2<=Y2){
A = max(A,tr[x].MAX);
B = min(B,tr[x].MIN);
return ;
}
if(tr[x].x1 == tr[x].x2 || tr[x].y1 == tr[x].y2){
query(X1,Y1,X2,Y2,son(x,0));
query(X1,Y1,X2,Y2,son(x,1));
}
else{
query(X1,Y1,X2,Y2,son(x,0));
query(X1,Y1,X2,Y2,son(x,1));
query(X1,Y1,X2,Y2,son(x,2));
query(X1,Y1,X2,Y2,son(x,3));
}
}
void updata(int X1,int Y1,int X2,int Y2,int x,int val){
if(tr[x].x1>X2||tr[x].x2Y2||tr[x].y2=X1&&tr[x].x2<=X2&&tr[x].y1>=Y1&&tr[x].y2<=Y2){
tr[x].MAX = val;
tr[x].MIN = val;
return ;
}
if(tr[x].x1 == tr[x].x2 || tr[x].y1 == tr[x].y2){
updata(X1,Y1,X2,Y2,son(x,0),val);
updata(X1,Y1,X2,Y2,son(x,1),val);
pushup2(x);
}
else{
updata(X1,Y1,X2,Y2,son(x,0),val);
updata(X1,Y1,X2,Y2,son(x,1),val);
updata(X1,Y1,X2,Y2,son(x,2),val);
updata(X1,Y1,X2,Y2,son(x,3),val);
pushup1(x);
}
}
int main(){
scanf("%d",&T);
for(int i = 1;i <= T; ++i){
scanf("%d",&n);
for(int i = 1;i <= n; ++i)
for(int j = 1;j <= n; ++j)
scanf("%d",&a[i][j]);
build(1,1,n,n,1);
scanf("%d",&q);
printf("Case #%d:\n",i);
int X1,X2,Y1,Y2;
while(q--){
scanf("%d %d %d",&x,&y,&L);
L = (L-1) >> 1;
X1 = max(1,x-L),Y1 = max(1,y-L);
X2 = min(n,x+L),Y2 = min(n,y+L);
A = 0,B = 1e9+19;
query(X1,Y1,X2,Y2,1);
updata(x,y,x,y,1,(A+B)>>1);
printf("%d\n",(A+B)>>1);
}
}
return 0;
}
2)树套树
先对X轴建一颗一维线段树,再在其每个节点下建一颗Y轴的一维线段树,那么X树的每个节点维护:x轴上[xl,xr],整个y轴的矩阵的信息,考虑如何向上维护信息(下图以纵为x)
(1)如果当前x节点为x树上的叶子节点,如X2 和 X3,那么它的y树就和普通的一维线段树一样维护
(2)如果当前x节点不是x树上的叶子节点,如X1,那么其y树的叶子节点是由它的儿子节点的y树的叶子节点合并而来
#include
using namespace std;
const int maxn = 805;
int MAX[maxn<<2][maxn<<2],MIN[maxn<<2][maxn<<2]; //第一维表示x树上节点的编号
int T,n,Q,a[maxn][maxn],xl,yl,xr,yr,x,y,L,newv,maxv,minv;
inline void pushupx(int x,int y){ //x树上的非叶子节点的y树的叶子节点通过儿子节点合并而来
MAX[x][y] = max(MAX[x<<1][y],MAX[x<<1|1][y]);
MIN[x][y] = min(MIN[x<<1][y],MIN[x<<1|1][y]);
}
inline void pushupy(int x,int y){
MAX[x][y] = max(MAX[x][y<<1],MAX[x][y<<1|1]);
MIN[x][y] = min(MIN[x][y<<1],MIN[x][y<<1|1]);
}
void buildy(int l,int r,int y,int x,int k){ //k标记是不是儿子节点
if(l == r){
if(k) MAX[x][y] = MIN[x][y] = a[k][r];
else pushupx(x,y);
return ;
}
int mid = (l+r) >> 1;
buildy(l,mid,y<<1,x,k);
buildy(mid+1,r,y<<1|1,x,k);
pushupy(x,y);
}
void buildx(int l,int r,int x){
if(l == r){
buildy(1,n,1,x,r);
return ;
}
int mid = (l+r) >> 1;
buildx(l,mid,x<<1);
buildx(mid+1,r,x<<1|1);
buildy(1,n,1,x,0);
}
void updatay(int l,int r,int y,int x,int k){
if(l > yr || r < yl) return;
if(l >= yl && r <= yr){
if(k) MAX[x][y] = MIN[x][y] = newv;
else pushupx(x,y);
return ;
}
int mid = (l+r) >> 1;
updatay(l,mid,y<<1,x,k);
updatay(mid+1,r,y<<1|1,x,k);
pushupy(x,y);
}
void updatax(int l,int r,int x){
if(l > xr || r < xl) return;
if(l >= xl && r <= xr){
updatay(1,n,1,x,r);
return ;
}
int mid = (l+r) >> 1;
updatax(l,mid,x<<1);
updatax(mid+1,r,x<<1|1);
updatay(1,n,1,x,0);
}
void queryy(int l,int r,int y,int x){
if(l > yr || r < yl) return ;
if( l >= yl && r <= yr){
maxv = max(maxv,MAX[x][y]);
minv = min(minv,MIN[x][y]);
return ;
}
int mid = (l+r) >> 1;
queryy(l,mid,y<<1,x);
queryy(mid+1,r,y<<1|1,x);
}
void queryx(int l,int r,int x){
if(l > xr || r < xl) return ;
if(l >= xl && r <= xr){
queryy(1,n,1,x);
return ;
}
int mid = (l+r) >> 1;
queryx(l,mid,x<<1);
queryx(mid+1,r,x<<1|1);
}
int main(){
scanf("%d",&T);
for(int i = 1;i <= T; ++i){
scanf("%d",&n);
for(int i = 1;i <= n; ++i)
for(int j = 1;j <= n; ++j)
scanf("%d",&a[i][j]);
buildx(1,n,1);
scanf("%d",&Q);
printf("Case #%d:\n",i);
while(Q--){
scanf("%d %d %d",&x,&y,&L);
L = (L-1) >> 1;
xl = max(1,x-L),yl = max(1,y-L);
xr = min(n,x+L),yr = min(n,y+L);
maxv = 0,minv = 1e9;
queryx(1,n,1);
newv = (maxv + minv) >> 1;
xl = xr = x;yl = yr = y;
updatax(1,n,1);
printf("%d\n",newv);
}
}
return 0;
}
两种方法单次操作的时间复杂度都是O(log^2),但第一种最差可以退化到O(n),此题第二种写法就比第一种快了近5倍