题目大意:给定N个带权值的点,M个询问,求每个询问中矩阵内点权之和。
数据范围:N,M <= 1e5,点坐标及权值均在int范围内
标签:树状数组,离散化,离线,扫描线
思路梳理:
如果数据范围小一点,那么这就是一道二维树状数组模板题。
我们不妨回忆一下,二维树状数组的查询操作:
res = query(X2,Y2) - query(X2,Y1 - 1) - query(X1 - 1,Y2) + query(X1 - 1,Y1 - 1);
类似地,我们也可以把本道题的询问拆成四个矩形来解决,记这些询问为新询问。
那么问题就转化为,如何求出给定矩阵的前缀和(实际上就是最小坐标一直到x,y组成的矩阵)。
但是数据范围过大,离散化后只允许开下一维的空间。怎么办?
考虑到没有强制在线,不妨将其离线,这样就可以有序化解决。
为什么有序化之后就可以解决了呢?
考虑扫描线。将给出点的坐标进行排序,一行一行进行扫描。
这样的话,扫描完第i行后,树状数组内存储的就是前i行的前缀和。
此时可以回答此行及以前的新询问。
接下来,解决如何离散化的问题。
考虑需要用到坐标的地方:
①更新:每个点的坐标将用于更新;
②查询:每个新询问的坐标将用于查询。
所以,我们不妨将这两组信息一起进行离散化,便于此后的查询操作。
至此,问题基本已经解决。
我们从头到尾梳理一遍代码的思路:
第一步,读入点坐标及询问坐标,将点坐标进行排序,将询问拆成四个新询问后进行排序;
第二步,将排序后的x坐标存进一个新的数组,注意保留原来的id及类型(点or询问)。排序后,将这些离散化后的x坐标存进数组,后期直接调用;
第三步,一行一行进行扫描,在扫描新点之前,回答答案已经确认的询问;
第四步,输出。
国际惯例,再考虑一下几个细节:
①数据类型?(被坑了不知多少次TAT)
虽然题目的信息都在int范围内,但要是数据一大,随便加一加马上就爆了。所以,必须开long long!
②数组空间?
着重考虑易出错的几个数组:树状数组、新询问数组、x坐标数组。
新询问已经扩大到原询问的4倍。此外,离散化是针对新询问及点坐标而言,故需开到N + (M<<2)(如果使用位运算,注意位运算的优先级比+-运算要低,所以要括号)
③树状数组循环上界?(因为这个,一开始导致一些答案没被统计进去…)
应该是离散后的最大点坐标,为N + 4 * M。
完整代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long//debug
inline int read(){
char c=getchar(); int f=1; int num=0;
while (c>'9'||c<'0'){if (c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9'){num=(num<<1)+(num<<3)+(c^48);c=getchar();}
return num*f;
}
inline void write(long long x, char c) {
if(x==0){putchar('0'); putchar(c); return;}
if(x<0){putchar('-'); x=-x;}
int len=0; char buf[20];
while(x){buf[++len]=x%10LL+48; x/=10LL;}
while(len) putchar(buf[len--]);
putchar(c);
return;
}
const int N = 1e5 + 10,M = 1e5 + 10;
int n,m;
struct node{
int x,y,p;
bool operator < (const node & a) const{
return y < a.y || y == a.y && x < a.x;
}
}sta[N];
struct nodex{
int xx,id,te;
bool operator < (const nodex & a) const{
return xx < a.xx;
}
}a[N + M * 4];
int tox1[N],tox2[M * 4];
struct nodeq{
int x,y,id,od;
bool operator < (const nodeq & a) const{
return y < a.y || y == a.y && x < a.x;
}
}q[M<<2];
int res[M];
int cntq,CNT;
int tr[N + 4 * M];//debug:空间要开够
void modify(int x,int p){
for(;x <= CNT;x += x & (-x)) //debug:循环上界
tr[x] += p;
}
int query(int x){
int sum = 0;
for(;x > 0;x -= x & (-x)) sum += tr[x];
return sum;
}
int tl[5] = {0,1,-1,-1,1};
signed main(){
n = read();m = read();
for(int i = 1;i <= n;i++)
sta[i].x = read(),sta[i].y = read(),sta[i].p = read();
sort(sta + 1,sta + n + 1);
for(int i = 1;i <= n;i++)
a[i].xx = sta[i].x,a[i].id = i,a[i].te = 1;
for(int i = 1;i <= m;i++){
int X1,Y1,X2,Y2;
X1 = read();Y1 = read();X2 = read();Y2 = read();
q[++cntq] = (nodeq){X1 - 1,Y1 - 1,i,1};
q[++cntq] = (nodeq){X1 - 1,Y2,i,2};
q[++cntq] = (nodeq){X2,Y1 - 1,i,3};//debug:注意-1
q[++cntq] = (nodeq){X2,Y2,i,4};
}
sort(q + 1,q + cntq + 1);
for(int i = 1;i <= cntq;i++)
a[n + i].xx = q[i].x,a[n + i].id = i,a[n + i].te = 2;
sort(a + 1,a + n + cntq + 1);
CNT = 1;//debug:注意不能从0开始,否则树状数组会死循环
for(int i = 1;i <= n + cntq;i++){
if(i > 1 && a[i].xx > a[i - 1].xx) CNT++;
if(a[i].te == 1) tox1[a[i].id] = CNT;
else tox2[a[i].id] = CNT;
}
int cnt = 0;
for(int i = 1;i <= n;i++){
while(cnt + 1 <= cntq && q[cnt + 1].y < sta[i].y){
cnt++;
res[q[cnt].id] += query(tox2[cnt]) * tl[q[cnt].od];
}//debug
modify(tox1[i],sta[i].p);
}
cnt++;//debug:cnt的新询问已经统计了,要+1才是还没统计的询问
for(;cnt <= cntq;cnt++)
res[q[cnt].id] += query(tox2[cnt]) * tl[q[cnt].od];
for(int i = 1;i <= m;i++)
write(res[i],'\n');
return 0;
}