关于二重数点
二重数点是这样一个问题:给一个长为 n 的序列,有 m 次查询,每次查区间 [l,r] 中值在 [x,y] 内的元素个数。
这种问题可以转化为在二维平面上查询一个左上端点为(l,x),右下端点为(r,y)的矩形内的点集大小。
于是便可以使用二维前缀和,扫描线,或树套树,或离散化后树状数组。
二维前缀和时间复杂度和空间复杂度都很高,这里离散化后树状数组是最简单的解决方式
例题:园丁的烦恼
题意:在花园里有 n 棵树,每一棵树可以用一个整数坐标来表示, m 个骑士们会来轮番询问你某一个矩阵内有多少树
n,m <= 5e5
x,y<=1e7
思路:看到坐标的范围便可意识到无法用前缀和来维护,考虑对于x与y两个维度,我们用容斥的想法,可以维护(c,d),(a-1,d),(c,b-1),(a-1,b-1),这四个坐标与(0,0)所形成矩阵的点集,最多需要维护2e6个点集,可以接受。
考虑先将坐标离散化,之后将每个点和询问都按x坐标从小到大排序,这样对于一个询问,我们就维护了所有x小于它的点的个数,此时我们只需用树状数组维护y小于它的点的个数便是该询问的答案。
代码容易实现:
#include<bits/stdc++.h>
using namespace std;
int const maxn = 3e6+10;
int const N = 1e7+10;
struct node{
int x,y,tg,id;
}p[maxn];
int qy[maxn];
int T[maxn],cnt;
int ans[maxn];
void newnode(int rt,int x,int y,int tag){
p[++cnt].id = rt;
p[cnt].x = x;
p[cnt].y = y;
p[cnt].tg = tag;
qy[cnt] = y;
}
int findt(int y){
int l = 1,r = cnt;
while(l < r){
int mid = (l+r)/2;
if(qy[mid] < y){
l = mid+1;
}else {
r = mid;
}
}
return r;
}
int lowbit(int x){
return x&(-x);
}
void add(int pos,int x){
for(int i = pos;i <= cnt;i +=lowbit(i)){
T[i]+=x;
}
}
int query(int pos){
int ans = 0;
for(int i = pos;i;i-=lowbit(i)){
ans += T[i];
}
return ans;
}
bool cmp(node a,node b){
if(a.x==b.x){
if(a.y==b.y){
return a.tg < b.tg;
}else {
return a.y < b.y;
}
}
return a.x < b.x;
}
signed main(){
ios::sync_with_stdio(false);
int n,m;
cin >> n >> m ;
for(int i = 1;i <= n;i ++){
int a,b;
cin >> a>>b;
newnode(i,a,b,0);
}
int pc = m;
for(int i = 1;i <= m;i ++){
int a,b,c,d;
cin >> a>>b>>c>>d;
newnode(i,a-1,b-1,1);
newnode(i+pc,a-1,d,1);
newnode(i+2*pc,c,b-1,1);
newnode(i+3*pc,c,d,1);
}
sort(p+1,p+cnt+1,cmp);
sort(qy+1,qy+cnt+1);
for(int i = 1;i <= cnt;i ++){
int pos =findt(p[i].y);
if(p[i].tg == 0){
add(pos,1);
}else {
ans[p[i].id]+=query(pos);
}
}
for(int i = 1;i <= m;i ++){
cout << ans[i]+ ans[i+3*pc]-ans[i+pc] - ans[i+2*pc] <<'\n';
}
return 0;
}
双倍经验:老C的任务

浙公网安备 33010602011771号