<h2>题目描述</h2>
平面上有 $n$ 个点 $(x_i, y_i)$,$m$ 次询问,每次询问为一个矩形内有多少点。 允许离线。
其中 $ n,m \leq 500000 $。
<h2>解题报告</h2>
首先这个东西显然可以二维前缀和来预处理。
具体来说就是设这个前缀和数组为 $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$ 坐标询问就可以了。
<h2>代码</h2>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <climits>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#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;
}