高中数学总要留下点什么才好……于是有了这题(其实搞的原题)。
平面直角坐标系(rectangular coordinate system):在同一个平面上互相垂直且有公共原点的两条数轴构成平面直角坐标系,简称为直角坐标系。通常情况下,两条数轴分别置于水平位置与竖直位置,取向右与向上的方向分别为两条数轴的正方向。水平的数轴叫做X轴或横轴,竖直的数轴叫做Y轴或纵轴,X轴或Y轴统称为坐标轴,它们的公共原点O称为直角坐标系的原点。建立了平面直角坐标系后,对于坐标系平面内的任何一点,我们可以确定它的坐标。反过来,对于任何一个坐标,我们可以在坐标平面内确定它所表示的一个点。
现在给出平面直角坐标系上的n个点。两个点能被一个边界平行于坐标轴的矩形所同时包含,且此矩形内不包含其余点,那么它们两个就能搞基(???)。要求的问题很简单,计算有多少对点能搞基。
这题,比较寻常的解法就是分治了。
但这里我不讲这个(其实分治是一种解决不可能问题的方向,理解了就可以了),我想讲一个另类的解法(某同学提供)
这里讲的解法不包含重点,当然,数据也没出,若有的话一开始判断一下即可。
这里,我们可以考虑一下,ans可能有多大?
这个问题我等会解释(这个和算法有很大关系)
好的,我们来考虑一下如何暴力获取答案,一般我们会发现,同一行(或同一列,这里我们视作同一行)的答案,可以由该行点数减1构成(即只由该行构成),也可以通过枚举该行某一点然后与其下的点组合,有效的组成方式可以如下图:
这个点能选的点的x范围受到同行相邻两个点的约束,而且若我们分成左半边与右半边的话,(这里只讨论左半边),我们可以发现它是一个y单调下降的点集,而且位于最高处。这样,我们可以通过扫描线+线段树维护每个x值得最高点,以及一个区间里最高点最左端、最右端的x值(有可能多个最高点,在左半边只取最靠右的)每次找到限制范围里最高点的位置,ans+1,然后收缩搜索范围(左半边即左限制向右移到找到的点的右侧)。
这里,我们每搜一次就可以使ans+1,没找到就break掉,那么时间复杂度就是anslogn+nlogn,ans期望比较小,因而可以过。
这就是为什么一开始就问答案会有多大,有的算法看上去不一定是最优的,但在与某些量形成关系后,也可以达到与题解相同的效果,这种关系很难有,但提前知道或有这方面的感觉对以后做题还是有好处的。
细节要处理好,例如左半边与右半边可能算重(点恰好在中间)或算到非法的(点在中间,假设右半边x限制不包含该点,那么该点对右半边的y也起到限制作用)
贴代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#define N 100001
#define MAXN 2000000000
using namespace std;
int n,ans,high,v,h;
int b[N],f[N*4][3];
bool bz[N*4];
struct node{
int x,y;
}a[N];
void init(){
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d %d",&a[i].x,&a[i].y),b[i]=a[i].x;
}
bool cmp(const node&a,const node&b){
return a.y<b.y||(a.y==b.y&&a.x<b.x);
}
void pre(){
sort(b+1,b+n+1);
b[0]=1;
for (int i=2;i<=n;i++)
if (b[b[0]]!=b[i])b[++b[0]]=b[i];
sort(a+1,a+n+1,cmp);
}
void findl(int l,int r,int s,int ll,int rr){
if (!bz[s])return;
if (b[l]>rr||b[r]<ll)return;
if (f[s][0]<high)return;
if (ll<=b[l]&&b[r]<=rr){
high=f[s][0],v=f[s][2];
return;
}
findl(l,(l+r)/2,s+s,ll,rr);
findl((l+r)/2+1,r,s+s+1,ll,rr);
}
void didl(int l,int r){
v=l-1;
high=-MAXN;
while (l<=r){
high=-MAXN;
findl(1,b[0],1,l,r);
if (v==l-1){
if (v==r)
h=high;
else
h=-MAXN;
return;
}
++ans;
l=v+1;
}
if (v==r)
h=high;
else
h=-MAXN;
}
void findr(int l,int r,int s,int ll,int rr){
if (!bz[s])return;
if (b[l]>rr||b[r]<ll)return;
if (f[s][0]<high)return;
if (ll<=b[l]&&b[r]<=rr){
high=f[s][0],v=f[s][1];
return;
}
findr((l+r)/2+1,r,s+s+1,ll,rr);
findr(l,(l+r)/2,s+s,ll,rr);
}
void didr(int l,int r){
v=r+1;
while (l<=r){
high=-MAXN;
findr(1,b[0],1,l,r);
if (v==r+1)return;
if (high<=h)return;
++ans;
r=v-1;
}
}
void ins(int l,int r,int s,int ll,int v){
if (!bz[s]){
bz[s]=1;
f[s][0]=v,
f[s][1]=f[s][2]=ll;
}else
if (f[s][0]<v)f[s][0]=v,f[s][1]=f[s][2]=ll;
else
f[s][1]=min(f[s][1],ll),f[s][2]=max(f[s][2],ll);
if (l==r)return;
static int ss;
if (b[ss=(l+r)/2]>=ll)ins(l,ss,s+s,ll,v);
else
ins(ss+1,r,s+s+1,ll,v);
}
void work(){
static int l,r;
l=1,r=1;
while (l<=n){
for (;r<=n&&a[r+1].y==a[l].y;r++);
if (l==r){
didl(b[1],a[l].x);
didr(a[l].x+1,b[b[0]]);
}else{
didl(b[1],a[l].x);
didr(a[l].x+1,a[l+1].x-1);
didl(a[r-1].x+1,a[r].x);
didr(a[r].x+1,b[b[0]]);
for (int i=l+1;i<r;i++)
didl(a[i-1].x+1,a[i].x),didr(a[i].x+1,a[i+1].x-1);
}
ans+=r-l;
for (int i=l;i<=r;i++)
ins(1,b[0],1,a[i].x,a[i].y);
l=r+1,r++;
}
}
void write(){
printf("%d",ans);
}
int main(){
init();
pre();
work();
write();
return 0;
}