二维线段树——区间最值系列

最近这几天学习了一下二维线段树,二维线段树主要有两种写法,四分树和树套树,暂时还没写过四分树,因为这个东西确实不常用,而且不好写也不好调。

树套树的写法思路其实不难,首先我们知道,我们通常的线段树的操作都是在线段上进行的,即一维的,当推广到二维上,即矩阵时,我们就把它叫做二维线段树。

二维线段树其实就是我们再操作时先找到对应x轴的区间(即第一维),之后再找到对应y轴的区间(即第二维),进行相应的操作。

其实就是我们先给x轴上的每个点维护一颗线段树,然后我们再在x轴上的每个点里面再维护一颗线段树记录y轴。其实就是线段树的每一个节点又同时是一颗线段树,很明显的树套树做法。当然挺好理解的,就是码量挺大的,而且空间花销很大,很容易MLE。

给出我的二维线段树(维护子矩阵最值和子矩阵求和和支持单点修改)的代码实现(可以当做模板来用):

/*
    注意空间问题
    二维线段树求区间最值问题(可修)。
    给你一个n*n的初始矩阵,支持三种操作    
    单点修改某个位置的值(或者加上一个值)
    查询子矩阵最小值和最大值,或者查询子矩阵的和
    测试题目:hdu 4819 && poj 1195
*/
#include 
#include 
#include 
#include 
#include 
#define ll long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1024 + 5;
ll MAX[N << 2][N << 2], minV, maxV,MIN[N<<2][N<<2];//维护最值
ll a[N<<2][N<<2];//初始矩阵
ll SUM[N<<2][N<<2],sumV;//维护求和
int n;
void pushupX(int deep, int rt)
{
    MAX[deep][rt] = max(MAX[deep << 1][rt], MAX[deep << 1 | 1][rt]);
    MIN[deep][rt] = min(MIN[deep << 1][rt], MIN[deep << 1 | 1][rt]);
    SUM[deep][rt] = SUM[deep<<1][rt]+SUM[deep<<1|1][rt];
}
void pushupY(int deep, int rt)
{
    MAX[deep][rt] = max(MAX[deep][rt << 1], MAX[deep][rt << 1 | 1]);
    MIN[deep][rt] = min(MIN[deep][rt << 1], MIN[deep][rt << 1 | 1]);
    SUM[deep][rt]=SUM[deep][rt<<1]+SUM[deep][rt<<1|1];
}
void buildY(int ly, int ry, int deep, int rt, int flag)
{
    //y轴范围ly,ry;deep,rt;标记flag
    if (ly == ry){
        if (flag!=-1)
            MAX[deep][rt] = MIN[deep][rt] = SUM[deep][rt] = a[flag][ly];
        else
            pushupX(deep, rt);
        return;
    }
    int m = (ly + ry) >> 1;
    buildY(ly, m, deep, rt << 1, flag);
    buildY(m + 1, ry, deep, rt << 1 | 1, flag);
    pushupY(deep, rt);
}
void buildX(int lx, int rx, int deep)
{
    //建树x轴范围lx,rx;deep
    if (lx == rx){
        buildY(1, n, deep, 1, lx);
        return;
    }
    int m = (lx + rx) >> 1;
    buildX(lx, m, deep << 1);
    buildX(m + 1, rx, deep << 1 | 1);
    buildY(1, n, deep, 1, -1);
}
void updateY(int Y, int val, int ly, int ry, int deep, int rt, int flag)
{
    //单点更新y坐标;更新值val;当前操作y的范围ly,ry;deep,rt;标记flag
    if (ly == ry){
        if (flag) //注意读清楚题意,看是单点修改值还是单点加值
            MAX[deep][rt] = MIN[deep][rt] = SUM[deep][rt] = val;
        else pushupX(deep, rt);
        return;
    }
    int m = (ly + ry) >> 1;
    if (Y <= m)
        updateY(Y, val, ly, m, deep, rt << 1, flag);
    else
        updateY(Y, val, m + 1, ry, deep, rt << 1 | 1, flag);
    pushupY(deep, rt);
}
void updateX(int X, int Y, int val, int lx, int rx, int deep)
{
    //单点更新范围x,y;更新值val;当前操作x的范围lx,rx;deep
    if (lx == rx){
        updateY(Y, val, 1, n, deep, 1, 1);
        return;
    }
    int m = (lx + rx) >> 1;
    if (X <= m)
        updateX(X, Y, val, lx, m, deep << 1);
    else
        updateX(X, Y, val, m + 1, rx, deep << 1 | 1);
    updateY(Y, val, 1, n, deep, 1, 0);
}
void queryY(int Yl, int Yr, int ly, int ry, int deep, int rt)
{
    //询问区间y轴范围y1,y2;当前操作y的范围ly,ry;deep,rt
    if (Yl <= ly && ry <= Yr)
    {
        minV = min(MIN[deep][rt], minV);
        maxV = max(MAX[deep][rt], maxV);
        sumV += SUM[deep][rt];
        return;
    }
    int m = (ly + ry) >> 1;
    if (Yl <= m)
        queryY(Yl, Yr, ly, m, deep, rt << 1);
    if (m < Yr)
        queryY(Yl, Yr, m + 1, ry, deep, rt << 1 | 1);
}
void queryX(int Xl, int Xr, int Yl, int Yr, int lx, int rx, int rt)
{
    //询问区间范围x1,x2,y1,y2;当前操作x的范围lx,rx;rt
    if (Xl <= lx && rx <= Xr){
        queryY(Yl, Yr, 1, n, rt, 1);
        return;
    }
    int m = (lx + rx) >> 1;
    if (Xl <= m)
        queryX(Xl, Xr, Yl, Yr, lx, m, rt << 1);
    if (m < Xr)
        queryX(Xl, Xr, Yl, Yr, m + 1, rx, rt << 1 | 1);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            scanf("%lld",&a[i][j]);
    buildX(1,n,1);
    int m;
    scanf("%d",&m);
    while(m--){
        int opt;
        scanf("%d",&opt);
        if(opt==1){ //单点修改
            int x,y,val;
            scanf("%d%d%d",&x,&y,&val);
            updateX(x,y,val,1,n,1);
        }
        else if(opt==2){//查询子矩阵最值,以及和
            minV=INF,maxV=-INF,sumV=0;//注意初始化
            int x1,y1,x2,y2;//注意看清楚给范围的方式以及下标是从0开始还是从1开始
            scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
            queryX(x1,x2,y1,y2,1,n,1);
            printf("%lld %lld %lld\n",sumV,minV,maxV);
        }
        
    }
    return 0;
}
/*
2 
2 2
2 2
3
2 1 1 2 2
1 1 1 5
2 1 1 2 2
*/

下面给出一个我自己拉的在virtual judge上的二维线段树专题的链接:https://vjudge.net/contest/353513#overview

你可能感兴趣的:(二维线段树)