题意:
给N (N<=500000)个点,每个点有x,y,z ( 0<= x,y,z <=10^9 )
对于某点(x,y,z),若存在一点(x1,y1,z1)使得x1 > x && y1 > y && z1 > z 则点(x,y,z)是特殊点。
问N个点中,有多少个特殊点。
思路:
大概就是,先记下所有点,从大到小排序,先按z的降序,再按y的降序,再按x的降序排序。
将x离散化,然后,按z分层,一层一层扫描。
Z表示当前层的z值。
线段树中,存的是所有 z > Z 的点,叶节点存下每个x上的最大y,然后线段树维护区间最大值。
在每层的Z搜索中:
操作一:遍历本层的点(x,y,z) ,在线段树中搜索横坐标大于x的点中,有没有纵坐标大于y的。
因为线段树中的点的z都大于Z,所以只需要搜索x和y,若存在,则这个点属于特殊点。
操作二:然后第二次遍历本层的点,将所有点加入线段树。
代码中,每层的操作二被延后到了下一层的操作一之前。
排序复杂度O(nlog2(n))
搜索复杂度O(nlog2(n))
总复杂度O(nlog2(n))
代码如下:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define maxn 500007
using namespace std;
int Rank[maxn],Rn;
void SetRank(){//[1..Rn]->[1..I]
int I=1;
sort(Rank+1,Rank+Rn+1);
for(int i=2;i<=Rn;++i) if(Rank[i]^Rank[i-1]) Rank[++I]=Rank[i];
Rn=I;
}
int GetRank(int x){
int L=1,R=Rn,M;//[L,R] first >= x
while(L^R){
M=(L+R)>>1;
if(Rank[M] < x)L=M+1;
else R=M;
}
return L;
}
int Max[maxn<<2],N;
int Build(int n){//建树
N=1;while(N < n+2) N <<= 1;
memset(Max,-1,sizeof(Max));
}
void Update(int L,int x){//用 x 更新 L 点的值
for(int s=N+L;s;s>>=1){
if(x > Max[s]) Max[s]=x;
else break;
}
}
bool Search(int L,int y){//搜索(L,Rn]有没有值大于 y
for(int s=N+L;s^1;s>>=1){
if(~s&1){
if(Max[s^1] > y) return true;
}
}
return false;
}
int n;
struct Point{
int x,y,z;
Point(){}
Point(int x,int y,int z):x(x),y(y),z(z){}
bool operator <(const Point &B)const{return z>B.z||z==B.z&&y>B.y||z==B.z&&y==B.y&&x>B.x;}
}P[maxn];
int main(void)
{
while(~scanf("%d",&n)){
Rn=0;
for(int i=0;i<n;++i){
scanf("%d",&P[i].x);
Rank[++Rn]=P[i].x;
}
for(int i=0;i<n;++i) scanf("%d",&P[i].y);
for(int i=0;i<n;++i) scanf("%d",&P[i].z);
//给点排序
sort(P,P+n);
//离散化
SetRank();
//建树
Build(Rn);
int ANS=0;
int Z,Zi=0;
int I=0;
while(I<n){//扫描
for(int i=Zi;i<I;++i){//更新线段树
Update(GetRank(P[i].x),P[i].y);
}
Z=P[I].z;//记录本层Z值
Zi=I;//记录本层起点,方便更新线段树
//计算
while(I < n && P[I].z==Z){
ANS+=Search(GetRank(P[I].x),P[I].y);
++I;
}
}
printf("%d\n",ANS);
}
return 0;
}