【模板篇】树状数组(五)

好的 我们之前说过了树状数组的
单点加区间查
区间加单点查
区间加区间查
和没什么用的单点改查区间最值

我们发现,这都是一维的树状数组,只能用来处理一维的东西..
我们就想,为什么不能把树状数组放到平面上呢?
然后就出现了二维树状数组

大概就是长这个样子(大雾)

int c[N][M];

(如果要画出他表示的样子大概要一个3D立体图,可是这个地方太小,写不下= =)

既然是树状数组,我们就不能忘记lowbit小朋友= =
他现在长这个样子

x&-x;

(其实并没有什么改动)

我们在二维中的单点指的就是坐标(x,y),区间指的就是以(x1,y1)为左上角(x2,y2)为右下角的矩形= =

我们已经知道,树状数组维护的是前缀和,所以二维树状数组中每个点查到的就是以(1,1)为左上角,这个点为右下角的矩形的区间和= =
所以我们单点加区间查可以这么写:

void add(int x,int y,int s)
    for(int i=x;i<=n;i+=lowbit(i))
        for(int j=y;j<=m;j+=lowbit(j)) //其实就是多了一重循环
            c[i][j]+=s;
int query(int x,int y){
    int s=0;
    for(int i=x;i;i-=lowbit(i))
        for(int j=y;j<=m;j+=lowbit(j))
            s+=c[i][j];
    return s;
}
//根据二维前缀和的性质,区间查是这样的
int _query(int x1,int y1,int x2,int y2){
    return query(x2,y2)-(query,x1-1,y2)-query(x2,y1-1)+query(x1-1,y2-1);
}

*唯一要说明的是,循环不能再像一维的一样写

for(;x;-=lowbit(x))
    for(;y;y-=lowbit(y))
        ...

因为x跑之后几遍的时候,y是不应该被修改的..然后就会出现玄学的错误= =

区间加单点查可以这么写:

void add(int x,int y,int s)
    for(int i=x;i<=n;i+=lowbit(i))
        for(int j=y;j<=m;j+=lowbit(j))
            c[i][j]+=s;
int query(int x,int y){
    int s=0;
    for(int i=x;i;i-=lowbit(i))
        for(int j=y;j<=m;j+=lowbit(j))
            s+=c[i][j];
    return s;
}
//二维差分
void _add(int x1,int x2,int y1,int y2,int s){
    add(x1,y1,s);
    add(x1,y2+1,-s);
    add(x2+1,y1,-s);
    add(x2+1,y2+1,s);
}

先这样吧= =
对于区间加单点查,贴一道例题(其实是因为做了这道题我才想到继续更树状数组的= =)
这个昨天的CodeForces上的一道比D题水多了的E题= =
http://codeforces.com/problemset/problem/869/E

本来自己的思路是对的,结果自己觉得不行就没有写,后来发现是自己nc…(我还是太菜了QAQ)
(虽然真正让我自己写还是会WA因为并没有意识到矩阵的编号会被卡OvO)
其实这题完全是可做的(虽然最后因为map的问题交了n遍才过= =)

题目大意(英文题,伤不起):
给一个n*m(0<=m,n<=2500)的空地,支持三种操作:
1. 在以(x1,y1)为左上角,(x2,y2)为右下角的矩形外侧建一堵墙..(不会盖到空地的最外围)
2. 拆除以(x1,y1)为左上角,(x2,y2)为右下角的矩形外侧的墙..(墙保证存在)
3. 查询(x1,y1)格与(x2,y2)格是否连通= =
其中,墙围保证没有公共点..

其实就是给每个矩阵编个号,1操作就把区间加这个编号,2操作就把这个区间减这个编号,3操作就查询这两个点的编号是否相等即可= =
对于每堵墙的编号,我们开个map存一下即可..

#include <map>
#include <cstdio>
#include <cstdlib>
using namespace std;
#define mp make_pair
typedef long long ll;
typedef pair<int,int> zb;
map<pair<zb,zb>,int> mmp;
inline int lb(int x){return x&-x;}
inline ll myrand(){return (ll)rand()<<15|rand();}
int n,m,q;
ll c[2505][2505];
inline int gn(){
    int a=0;char c=getchar();for(;c<'0'||c>'9';c=getchar());
    for(;c>='0'&&c<='9';c=getchar())a=(a<<1)+(a<<3)+c-'0';return a;
}
void _add(int x,int y,ll s){
    for(int i=x;i<=n;i+=lb(i))
        for(int j=y;j<=m;j+=lb(j))
             c[i][j]+=s;
}
ll query(int x,int y){
    ll s=0;
    for(int i=x;i;i-=lb(i))
        for(int j=y;j;j-=lb(j))
            s+=c[i][j];
    return s;
}
void add(int x1,int y1,int x2,int y2,ll s){
    _add(x1,y1,s); _add(x1,y2+1,-s); _add(x2+1,y1,-s); _add(x2+1,y2+1,s);
}
int main(){
    srand(0x1204); //这个地方的种子大约是没有什么太大问题的..
    n=gn();m=gn();q=gn();
    while(q--){
        int opt=gn(),x1=gn(),y1=gn(),x2=gn(),y2=gn();
        switch(opt){
            case 1:{
                ll k=myrand(); 
                //这个地方不能简单的insert,因为可能会出现拆了又盖 盖了又拆的情况,如果不erase,
                //后面的insert就会沦为无效操作= = 就会在第36个点WA...
                mmp[mp(mp(x1,y1),mp(x2,y2))]=k;
//              mmp.insert(mp(mp(mp(x1,y1),mp(x2,y2)),k));
//              add(x1,y1,x2,y2,k);
                break;
            }
            case 2:{
                //如果上面选择了insert那么这里就要erase掉
//              pair<zb,zb> pr=mp(mp(x1,y1),mp(x2,y2));
//              int k=mmp[pr];
//              mmp.erase(pr);
                int k=mmp[mp(mp(x1,y1),mp(x2,y2))];
                add(x1,y1,x2,y2,-k);
                break;
            }
            default:{
                ll q1=query(x1,y1),q2=query(x2,y2);
                if(q1==q2) puts("Yes"); else puts("No");
                break;
            }
        }
    }
}

哒哒哒~~

原文地址:https://www.cnblogs.com/enzymii/p/8412134.html