3195. 【HNOI模拟题】数学(分治 + 单调栈二分)

https://jzoj.net/senior/#main/show/3195

Problem
  • 给定平面上 n n n个点,求有多少对点能被一个平行于矩形包含,且仅仅包含这两个点。
Data constraint
  • n ≤ 1 0 5 n\le 10^5 n105
Solution
  • 首先吐槽一下搬题人。你搬题就搬嘛,还非要搞点别的,原题的 x , y x,y x,y互不相同,这里可以相同,并且,矩形还可以是就一行或者就一列的,数据又水,50分根本发现不出来,结果调了好久才发现暴力都是错的。

  • 经典套路就是分治了。

  • 但是注意,为了不算重并且更好打,我们应该按值来分治,不要按序列来分治。分治完之后我们注意到,左边维护一个 y y y递减的情况下满足 x x x递减的单调栈,右边维护一个 y y y体检的情况下 x x x递增的一个单调栈。然后每次lower_bound一下可以找到合法的一段区间,统计答案即可。

  • 细节很多,注意特殊处理在同一行和同一列的情况。

Code
#include <bits/stdc++.h>
#define F(i,a,b) for(int i=a;i<=(b);i++)
const int N=1e5+10;
using namespace std;
int lx[N],ly[N],rx[N],ry[N];
int n,MN,MX,Ans,dw,up,cnt,lasx,lasy;
struct node{int x,y;}d[N],Lef[N],Rig[N];
bool cmx(node a,node b) {return a.x<b.x||a.x==b.x&&a.y<b.y;}
bool cmy1(node a,node b) {return a.y>b.y||a.y==b.y&&a.x>b.x;}
bool cmy2(node a,node b) {return a.y>b.y||a.y==b.y&&a.x<b.x;}
int Find(int x,int y,int r,int n){
	int l=1, Ans=0;
	while(l<=r){
		int m=l+r>>1;
		if ((!y&&ly[m]>=x&&lx[m]<n)||(y&&ry[m]>=x&&rx[m]>n)) Ans=m, l=m+1; else r=m-1;
	}
	return Ans;
}
void CDQ(int ll,int rr,int l,int r){
	if(ll>=rr||l>=r)return;
	int mm=ll+rr>>1,m=l+r>>1,L=0,R=0,LL=1,RR=1;
	while(d[m].x>mm)m--;
	while(d[m+1].x<=mm)m++;
	F(i,l,m) Lef[++L]=d[i];
	F(i,m+1,r) Rig[++R]=d[i];
	if (L==0||R==0) {CDQ(ll,mm,l,m),CDQ(mm+1,rr,m+1,r);return;}
	sort(Lef+1,Lef+L+1,cmy1);
	sort(Rig+1,Rig+R+1,cmy2);
	F(i,2,L)
		if (Lef[i].y^Lef[LL].y) Lef[++LL]=Lef[i];
	F(i,2,R)
		if (Rig[i].y^Rig[RR].y) Rig[++RR]=Rig[i];
	if(LL==0||RR==0)return;
	L=R=0,Lef[++LL]={-1e9,-1e9};
	for (int i=1,j=1;i<=LL;i++){
		while(j<=RR&&Rig[j].y>=Lef[i].y){
			if (Rig[j].y==Lef[i].y)
				while(L&&Lef[i].x>=lx[L]) L--;
			lasx=lasy=1e9;
			while(R&&Rig[j].x<=rx[R]) {
				if (Rig[j].x==rx[R]) lasy=ry[R], lasx=rx[R];
				R--;
			}
			if (lasy==1e9)
				lasx=rx[R], lasy=ry[R];
			rx[++R]=Rig[j].x, ry[R]=Rig[j].y;
			dw=Find(ry[R],0,L,rx[R]), up=Find(lasy,0,L,lasx);
			if (ly[dw]==ry[R]) dw--;
			Ans+=dw-up;
			j++;
		}
		if (i==LL) break;
		lasy=1e9;
		while(L&&Lef[i].x>=lx[L]) {
			if (Lef[i].x==lx[L]) lasy=ly[L];
			L--;
		}
		if (lasy==1e9)
			lasy=ly[L];
		lx[++L]=Lef[i].x, ly[L]=Lef[i].y;
		dw=Find(ly[L],1,R,lx[L]), up=Find(lasy,1,R,-1e9);
		if (ry[dw]==ly[L]) dw--;
		Ans+=dw-up;
	}
	F(i,1,max(LL,RR)) rx[i]=ry[i]=lx[i]=ly[i]=0;
	CDQ(ll,mm,l,m),CDQ(mm+1,rr,m+1,r);
}
int main(){
	scanf("%d",&n),ry[0]=ly[0]=MN=1e9;
	F(i,1,n) scanf("%d%d",&d[i].x,&d[i].y),MN=min(MN,d[i].x),MX=max(MX,d[i].x);
	sort(d+1,d+n+1,cmx);
	F(i,1,n)
		if(d[i].x==d[i+1].x) cnt++;
		else Ans+=cnt, cnt=0;
	CDQ(MN,MX,1,n);
	sort(d+1,d+n+1,cmy1);
	F(i,1,n)
		if(d[i].y==d[i-1].y) cnt++;
		else Ans+=cnt, cnt=0;
	printf("%d\n",Ans+cnt);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值