提高培优班Day19T3

题意

在一个二维平面上,给定点集 $S$,求所有满足

  • \large{x_i\lt x_j}
  • \large{y_i\lt y_j}
  • \large{\forall k\in S\cap\{i,j\}} ,有 \large{x_k\notin[x_i,x_j] , y_k\notin [y_i , y_j]}

的点对 (i,j) 的数量。

Solution

发现对于一个点 i 而言,对答案的贡献应为在它右上方的点所形成的凸包的大小。即其右上方 x 最接近于 x_iy 最接近于 y_i 的两个点之间形成的类下凸包大小。令二者分别为 pre_ir_i

形式化地, \forall k\in T_i ,有 x_i\lt x_k,y_i\lt y_k

pre_i \in T_i\forall k \in T_i ,有 \lvert x_{pre_{j}}-x_i\lvert \leq \lvert x_k-x_i\lvert

同样地,有 r_i \in T_i\forall k \in T_i ,有 \lvert y_{r_{j}}-y_i\lvert \leq \lvert y_k-y_i\lvert

则对答案可以产生贡献的点集为 pre_i,a_1,\cdots,a_p,r_i ,满足 a_i\in T_ix_{a_{i-1}}\lt x_{a_i}\lt x_{a_{i+1}} ,且 y_{a_{i-1}}\lt y_{a_i}\lt y_{a_{i+1}}

对于每个数,单调栈维护类下凸包即可,时间复杂度为 \mathcal{O}(n^2)

考虑优化,发现不必要对于每个点重新计算答案,考虑对于 xy 区间分治并合并答案。以分治 x 坐标为例,从 y_{max} 向下不断枚举。对于 [mid+1,right] 区间,维护一个类下凸包,每个点满足在前一个点的右下方。对于 [left,mid] 区间,也维护一个类下凸包,每个点满足在前一个点的左下方,对于新加入的点 i ,在类下凸包中的前一个点对 i 对答案贡献的影响最大,即前文提到的 pre_i 。在右区间的类下凸包中,所有 y\ge y_{pre_{i}} 的点都不产生贡献了,可以在右区间的单调栈中二分找到分界。

时间复杂度为 \mathcal{O}(n\log^2n) ,可以通过本题。

code

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int res=0,flag=1;
	char ch=getchar();
	while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
	while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
	return res*flag;
}
struct Stack
{
	int head;
	std::pair<int,int> val[200010];
	void push(pair<int,int> value)
	{
		val[++head]=value;
		return ;
	}
	void pop() { head--; return ; }
	pair<int,int> top() { return val[head]; }
	bool empty() { return !head; }
	void clear() { head=0; return ; }
	int solve(int pos)
	{
		int left=1,right=head,res=head+1;
		while(left<=right)
		{
			int mid=(left+right)>>1;
			if(val[mid].second<pos)
				res=mid,right=mid-1;
			else
				left=mid+1;
		}
		if(res==head+1)
			return 0;
		return head-res+1;
	}
};
struct Stack a,b;
int n;
long long ans;
void solve(int left,int right,vector<pair<int,int> > val)
{
	b.push(make_pair(0x3f3f3f3f,0x3f3f3f3f));
	if(left==right)
	{
		ans+=val.size()-1;
		return ;
	}
	int mid=(left+right)>>1;
	vector<pair<int,int> > l,r;
	l.clear(),r.clear();
	for(auto i=val.begin();i!=val.end();i++)
	{
		if(i->first<=mid)
		{
			l.push_back(*i);
			while(b.empty()==false&&b.top().first<i->first)
				b.pop();
			ans+=a.solve(b.top().second);
			b.push(*i);
		}
		if(i->first>mid)
		{
			r.push_back(*i);
			while(a.empty()==false&&a.top().first>=i->first)
				a.pop();
			a.push(*i);
		}
	}
	a.clear(),b.clear();
	if(l.size()>=1) solve(left,mid,l);
	if(r.size()>=1) solve(mid+1,right,r);
	return ;
}
int main(int argc,const char *argv[])
{
	n=read();
	int left=INT_MAX,right=0;
	vector<pair<int,int> > val;
	for(int i=1;i<=n;i++)
	{
		int x=read(),y=read();
		val.push_back(make_pair(x,y));
		left=std::min(left,x);
		right=std::max(right,x);
	}
	std::sort(val.begin(),val.end(),[](pair<int,int> a,pair<int,int> b){return (a.second==b.second)?(a.first>b.first):(a.second>b.second);});
	solve(left,right,val);
	printf("%lld\n",ans);
	return 0;
}
5 个赞