「SHOI2007」园丁的烦恼

发布于 2018-10-01  299 次阅读


题目描述

题目链接

平面上有 $n$ 个点 $(x_i, y_i)$,$m$ 次询问,每次询问为一个矩形内有多少点。 允许离线。

其中 $ n,m \leq 500000 $。

解题报告

首先这个东西显然可以二维前缀和来预处理。

具体来说就是设这个前缀和数组为 $S$,每次暴力预处理,暴力查询矩形就可以了。

每次查询矩形$(a,b),(c,d)$:$ans=S(c,d) - S(a,d) - S(b,c) + S(a,b)$。

时间复杂度是 $O(n^2)$,无法接受。

让我们考虑扫描线。

形式化来讲,我们不管是查询还是树的位置都看作一些点,然后将所有询问离线,以 $x$ 坐标为关键字排序。这样我们将其降维了。

这样降维之后可以看成两种操作:

  • 插入一个点
  • 询问答案

可以用树状数组维护。

具体来说,树状数组维护的是 $y$ 坐标为 $[0,y]$ 的点的个数(这个显然是可减的,类似于前缀和),然后扫一遍这些点,遇到代表树的点先将其扔进树状数组里,遇到每个询问就按照上面的式子直接对关于的 $y$ 坐标询问就可以了。

代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define Re register
#define LL long long
#define U unsigned
#define FOR(i,a,b) for(Re int i = a;i <= b;++i)
#define RFOR(i,a,b) for(Re int i = a;i >= b;--i)
#define SFOR(i,a,b,c) for(Re int i = a;i <= b;i+=c)
#define CLR(i,a) memset(i,a,sizeof(i))
#define BR printf("--------------------\n")
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

namespace fastIO{ 
    #define BUF_SIZE 100000 
    #define OUT_SIZE 100000 
    #define ll long long 
    bool IOerror=0; 
    inline char nc(){ 
        static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE; 
        if (p1==pend){ 
            p1=buf; pend=buf+fread(buf,1,BUF_SIZE,stdin); 
            if (pend==p1){IOerror=1;return -1;} 
        } 
        return *p1++; 
    } 
    inline bool blank(char ch){return ch==' '||ch=='\n'||ch=='\r'||ch=='\t';} 
    inline void read(int &x){ 
        bool sign=0; char ch=nc(); x=0; 
        for (;blank(ch);ch=nc()); 
        if (IOerror)return; 
        if (ch=='-')sign=1,ch=nc(); 
        for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0'; 
        if (sign)x=-x; 
    } 
    inline void read(ll &x){ 
        bool sign=0; char ch=nc(); x=0; 
        for (;blank(ch);ch=nc()); 
        if (IOerror)return; 
        if (ch=='-')sign=1,ch=nc(); 
        for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0'; 
        if (sign)x=-x; 
    } 
    #undef ll 
    #undef OUT_SIZE 
    #undef BUF_SIZE 
}; 
using namespace fastIO;

const int MAXN = 500000 + 5;

int N,M;

struct Point{
    int x,y,opt,pos;
    // x 和 y 描述点坐标,opt 和 pos 是询问特有的:opt 表示当前这个点对这个询问的贡献是加还是减,pos 表示当前这个点在原先的询问顺序。
    bool operator < (const Point &other) const {
        return x < other.x || x == other.x && y < other.y || x == other.x && y == other.y && !opt;
    }
}p[MAXN<<3],*last = p;
int size;

inline void *New(int x,int y,int opt=0,int pos=0){ // 日常一指针
    ++size;
    Point *ret = ++last;
    last->x = x;last->y = y;
    last->pos = pos;last->opt = opt;
}

// Bit Tree

int tree[MAXN << 2],maxy;
#define lowbit(x) x&-x

inline void add(int pos,int delta){
    while(pos <= maxy+1){
        tree[pos] += delta;
        pos += lowbit(pos);
    }
}

inline int calc(int pos){
    int ret = 0;
    while(pos){
        ret += tree[pos];
        pos -= lowbit(pos);
    }
    return ret;
}
#undef lowbit

int ans[MAXN];
// 坐标++是因为使用了树状数组,想想这是为什么。
int main(){
    read(N);read(M);
    FOR(i,1,N){
        int x,y;
        read(x);read(y);
        x++;y++; 
        New(x,y);
        maxy = std::max(maxy,y); // 确定上界
    }
    FOR(i,1,M){
        int a,b,c,d;
        read(a);read(b);read(c);read(d);
        New(a,b,1,i);
        New(c+1,d+1,1,i);
        New(a,d+1,-1,i);
        New(c+1,b,-1,i);
        // F(c,d) - F(a,c) - F(b,c) + F(a,b)
    }
    std::sort(p + 1,p + size + 1);
    FOR(i,1,size){
        if(!p[i].opt)
            add(p[i].y,1);
        else ans[p[i].pos] += p[i].opt * calc(p[i].y);
    }
    FOR(i,1,M) printf("%d%c",ans[i],'\n');
    return 0;
}

一个OIer。