[二维线段树] [二维树状数组] [Vijos P1512] SuperBrother打鼹鼠 (mole)

Background 背景

SuperBrother在机房里闲着没事干(再对比一下他的NOIP,真是讽刺啊……),于是便无聊地开始玩“打鼹鼠”……

Description 描述

在这个“打鼹鼠”的游戏中,鼹鼠会不时地从洞中钻出来,不过不会从洞口钻进去(鼹鼠真胆大……)。洞口都在一个大小为 n(n1024) 的正方形中。这个正方形在一个平面直角坐标系中,左下角为 (0,0) ,右上角为 (n1,n1) 。洞口所在的位置都是整点,就是横纵坐标都为整数的点。而SuperBrother也不时地会想知道某一个范围的鼹鼠总数。这就是你的任务。

Input 输入

每个输入文件有多行。
第一行,一个数 n ,表示鼹鼠的范围。
以后每一行开头都有一个数 m ,表示不同的操作:
m=1 ,那么后面跟着3个数 x,y,k(0x,y<n) ,表示在点 (x,y) 处新出现了 k 只鼹鼠;
m=2 ,那么后面跟着 4 个数 x1,y1,x2,y2(0x1x2<n,0y1y2<n) ,表示询问矩形 (x1,y1)(x2,y2) 内的鼹鼠数量;
m=3 ,表示老师来了,不能玩了。保证这个数会在输入的最后一行。
询问数不会超过 10000 ,鼹鼠数不会超过maxlongint。
//maxlongint=INT_MAX

Output 输出

对于每个 m=2 ,输出一行数,这行数只有一个数,即所询问的区域内鼹鼠的个数。

Sample Input 样例输入

4
1 2 2 5
2 0 0 2 3
3

Sample Output 样例输出

5

Limits 限制

1s 128MB

这道题刚看起来像是线段树,只不过要维护的东西不是一维数轴上的点,而是二维平面直角坐标系中的有序数对。(学过数学的)我们都知道,平面直角坐标系由两个互相垂直的数轴构成,所以在一维线段树下再维护一个线段树(类似于树套树),即可搞定。凭着初生牛犊不怕虎(其实是敬业)的精神,耗了 4h+ 搞定这个(鬼畜的)二维线段树

#include 
#include 
#define MAXN 1025
using namespace std;

struct node
{
    int y_left,y_right;
    int middle;
    int sum;
};

struct node1
{
    int x_left,x_right;
    int middle;
    node sub[2*MAXN];
};

node1 tree[2*MAXN];
int n,m;
int x1,x2,y1,y2,k;
//建树思路:建一维,再建一维
//这回线段树是左闭右闭,因为左闭右开的线段树写着写着脑抽了......
//其实左闭右闭也挺好写,记着就好了......
void Build_Sub_Tree(int x_id,int y_id,int y_l,int y_r) //第二维建树
{
    tree[x_id].sub[y_id].y_left=y_l;
    tree[x_id].sub[y_id].y_right=y_r;
    if(y_l==y_r) return ;
    int mid=(y_l+y_r)>>1;
    tree[x_id].sub[y_id].middle=mid;
    Build_Sub_Tree(x_id,y_id<<1,y_l,mid);
    Build_Sub_Tree(x_id,y_id<<1|1,mid+1,y_r);
}

void Build_A_Tree(int x_id,int x_l,int x_r,int y_l,int y_r)//第一维建树
{
    tree[x_id].x_left=x_l;
    tree[x_id].x_right=x_r;
    Build_Sub_Tree(x_id,1,y_l,y_r);
    if(x_l==x_r)return ;
    int mid=(x_l+x_r)>>1;
    tree[x_id].middle=mid;
    Build_A_Tree(x_id<<1,x_l,mid,y_l,y_r);
    Build_A_Tree(x_id<<1|1,mid+1,x_r,y_l,y_r);
}
//改值思路:改一维,再改一维
void modify_y(int x_id,int y_id,int y,int k)//第二维修改
{
    tree[x_id].sub[y_id].sum+=k;//一个位置可能出现多次鼹鼠
    if(tree[x_id].sub[y_id].y_left==tree[x_id].sub[y_id].y_right)
        return;
    if (y<=tree[x_id].sub[y_id].middle)
        modify_y(x_id,y_id<<1,y,k);
    else modify_y(x_id,y_id<<1|1,y,k);
    tree[x_id].sub[y_id].sum=tree[x_id].sub[y_id<<1].sum+tree[x_id].sub[y_id<<1|1].sum;
}

void modify_x(int x_id,int x,int y,int k)//第一维修改
{
    modify_y(x_id,1,y,k);
    if (tree[x_id].x_left==tree[x_id].x_right)
        return;
    if (x<=tree[x_id].middle) modify_x(x_id<<1,x,y,k);
    else modify_x(x_id<<1|1,x,y,k);
}
//查询思路:查一维,再查一维
int query_y(int x_id,int y_id,int y1,int y2)//查找第二维
{ 
    if(tree[x_id].sub[y_id].y_left==y1&&tree[x_id].sub[y_id].y_right==y2)
        return tree[x_id].sub[y_id].sum;
    if (tree[x_id].sub[y_id].middle>=y2) 
        return query_y(x_id,y_id<<1,y1,y2);
    else if (tree[x_id].sub[y_id].middlereturn query_y(x_id,y_id<<1|1,y1,y2);
    else return query_y(x_id,y_id<<1,y1,tree[x_id].sub[y_id].middle)+query_y(x_id,y_id<<1|1,tree[x_id].sub[y_id].middle+1,y2);
}

int query_x(int x_id,int x1,int y1,int x2,int y2) //查找第一维
{
    if (tree[x_id].x_left==x1&&tree[x_id].x_right==x2)return query_y(x_id,1,y1,y2);
    if (tree[x_id].middle>=x2) return query_x(x_id<<1,x1,y1,x2,y2);
    else if (tree[x_id].middlereturn query_x(x_id<<1|1,x1,y1,x2,y2);
    else return query_x(x_id<<1,x1,y1,tree[x_id].middle,y2)+query_x(x_id<<1|1,tree[x_id].middle+1,y1,x2,y2);
}

int main()
{
    scanf("%d",&n);
    Build_A_Tree(1,1,n+1,1,n+1);
    while(scanf("%d",&m)!=EOF)
    {
        if (m==1)
        {
            scanf("%d %d %d",&x1,&y1,&k);
            modify_x(1,x1+1,y1+1,k);
        }
        else if (m==2)
        {
            scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
            if (x1>x2) swap(x1,x2);//查询一定注意这里!!!!
            if (y1>y2) swap(y1,y2);
            printf("%d\n",query_x(1,x1+1,y1+1,x2+1,y2+1));
        }
        else break;
    }
    return 0;
}

大家是不是看吐了?
这个代码在VJ上 370ms 66320KB ,显然在代码实现和时空复杂度上还是不够好(我要 0ms ……)
于是,一个神奇的东西出现了,树状数组
其实,本题是树状数组的一道大水题!!!!!
大家比较一下(请大家不要过于激动……)

#include 
#define MAXN 1200
using namespace std;

int c[MAXN][MAXN];
int n,m;
int x1,x2,y1,y2;
int k;

int lowbit(int x)//lowbit操作是降低复杂的的关键
{
    return x&(-x);
}

int sum(int x,int y)
{
    int ret=0;
    for (int i=y;i;i-=lowbit(i))
        for (int j=x;j;j-=lowbit(j))
        ret+=c[i][j];
    return ret;
}

void add(int x,int y,int delta)
{
    for (int i=y;i<=n;i+=lowbit(i))
        for (int j=x;j<=n;j+=lowbit(j))
        c[i][j]+=delta;
}

int main()
{
    scanf("%d",&n);
    while (scanf("%d",&m)!=EOF)
    {
        if (m==1)
        {
            scanf("%d %d %d",&x1,&y1,&k);
            add(x1+1,y1+1,k);
        }
        else if (m==2)
        {
            scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
            printf("%d\n",sum(x2+1,y2+1)-sum(x1,y2+1)-sum(x2+1,y1)+sum(x1,y1));
   //小小的运用了一下面积的割补法,题中区域内鼹鼠个数包括边界上的,切记
        }
        else break;
    }
    return 0;
}

VJ上 60ms 6140KB ……
是不是突然觉得二维线段树白写了……
关于树状数组的介绍,由于篇幅有限请自行度娘
关于二维线段树是不是白写了,额……,本人不置可否
//本人观点:在这道题上是,但是说不定哪天就碰上一道写二维线段树的题呢

你可能感兴趣的:(线段树,树状数组)