【XSY3479】子区间(扫描线)

题意:转化后变为:平面上给定 n n n 个关键点, q q q 次询问一个点与其左上的每个关键点形成的矩形面积的最大值。

题解:

挺玄妙的一题。

这里假设这 n n n 个关键点都是 x x x 单调不降且 y y y 单调不降的(因为若点 A A A 在点 B B B 左上方则点 B B B 肯定无用),注意这是个壳,但不一定是凸的。

由于不一定是凸的,你发现它们与询问点之间的矩形面积不是单峰的,所以不能直接二分。

先考虑只有两个关键点的情况,考虑两个关键点各自的控制范围,解出来边界是一条经过两点连线中点,且与两点连线对称的直线,称其为这两个关键点的划分直线。两个关键点分居平面上被这条直线所划分出来的两个区域,询问点与和它在同一个区域的关键点所形成的矩形面积更大(虽然有点反直觉),即一个关键点控制了它所在区域的所有询问点。

现在有多个关键点时,将所有关键点按 x x x 坐标排序,考虑每相邻两个关键点所形成的划分直线,如果这些直线不交,此时关键点们与询问点之间的矩形面积就是单峰的,可以直接二分。

发现若两条相邻直线相交(注意最先相交的一定是某两条相邻的直线),假设它们分别是 A , B A,B A,B 和 B , C B,C B,C 的划分直线,那么对于横坐标比交点大的询问点,点 B B B 对于它都是没有贡献的,即点 B B B 的控制范围不会在交点之后,可以参照下图理解:

【XSY3479】子区间(扫描线)

也就是说在交点坐标之后,我们就可以把点 B B B 删掉了。

那么考虑扫描线,把所有点按 x x x 坐标从小到大排序,动态维护当前扫描线 x = X x=X x=X 上的控制区域划分,遇到划分直线的交点时就更新控制区域。

【XSY3479】子区间(扫描线)

如图,每个点的控制区域已经被我们用颜色标明(无敌的 mathematica!),假设当前扫描线在 x = X x=X x=X,那么我们就要维护图中的三条蓝色直线,然后遇到询问时直接二分询问点被夹在哪两条直线之间、遇到交点时我们更新这些蓝色直线即可。

时间复杂度 O ( ( n + q ) log ⁡ n ) O((n+q)\log n) O((n+q)logn)。

原题需要对时间分治,时间复杂度为 O ( q log ⁡ 2 q ) O(q\log ^2q) O(qlog2q)。

#include<bits/stdc++.h>

#define db double
#define N 400010
#define ll long long

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

struct Point
{
	db x,y;
	Point(){};
	Point(db _x,db _y){x=_x,y=_y;}
}S[N],Q[N];

Point operator - (Point a,Point b){return Point(a.x-b.x,a.y-b.y);}

struct Line
{
	db k,b;
	Line(){};
	Line(db _k,db _b){k=_k,b=_b;}
	Line(Point p1,Point p2)
	{
		k=(p1.y-p2.y)/(p1.x-p2.x);
		b=p1.y-k*p1.x;
	}
	db gety(db x){return k*x+b;}
};

Point intersection(Line l1,Line l2)
{
	db x=(l1.b-l2.b)/(l2.k-l1.k);
	db y=l1.k*x+l1.b;
	return Point(x,y);
}

int m,np,nq;
ll ans[N];

namespace Solve
{
	db X;
	struct data
	{
		Point p;
		int id;
		data(){};
		data(Point a,int b){p=a,id=b;}
	}p[N<<1];
	int cnt;
	void getp(vector<int> &pid)
	{
		sort(pid.begin(),pid.end(),[&](int a,int b)
		{
			if(S[a].x==S[b].x) return S[a].y>S[b].y;
			return S[a].x<S[b].x; 
		});
		int top=-1;
		for(auto i:pid)
		{
			if(top!=-1&&S[i].y<=S[top].y) continue;
			p[++cnt]=data(S[i],-i),top=i;
		}
	}
	int pre[N<<1],nxt[N<<1],tail;
	Line getLine(int a,int b){return Line(Point(p[a].p.x,p[b].p.y),Point(p[b].p.x,p[a].p.y));}
	struct Div
	{
		Line l;
		int id;
		Div(){};
		Div(Line _l,int _id){l=_l,id=_id;}
	};
	struct cmp
	{
		bool operator () (Div a,Div b) const
		{
			return a.l.gety(X)<b.l.gety(X);
		}
	};
	set<Div,cmp>s;
	struct Inter
	{
		db x;
		int mid;
		Inter(){};
		Inter(db a,int b){x=a,mid=b;}
	};
	bool operator < (Inter a,Inter b)
	{
		if(a.x==b.x) return a.mid<b.mid;
		return a.x<b.x;
	}
	set<Inter>heap;
	void update(int a,int b,int c,int opt)
	{
		if(Line(p[b].p,p[c].p).k<=Line(p[a].p,p[b].p).k) return;
		Line l1=getLine(a,b),l2=getLine(b,c);
		db inter=intersection(l1,l2).x;
		if(opt==1) heap.insert(Inter(inter,b));
		else heap.erase(Inter(inter,b));
	}
	void check(db nx)
	{
		while(!heap.empty()&&(*heap.begin()).x<=nx)
		{
			Inter now=(*heap.begin());
			int b=now.mid,a=pre[b],c=nxt[b];
			if(pre[a]) update(pre[a],a,b,-1);
			update(a,b,c,-1);
			if(nxt[c]) update(b,c,nxt[c],-1);
			pre[c]=a,nxt[a]=c;
			if(pre[a]) update(pre[a],a,c,1);
			if(nxt[c]) update(a,c,nxt[c],1);
			s.erase(Div(getLine(a,b),a)),s.erase(Div(getLine(b,c),b));
			s.insert(Div(getLine(a,c),a));
		}
	}
	void push(int id)
	{
		if(tail)
		{
			pre[id]=tail,nxt[tail]=id;
			if(pre[tail]) update(pre[tail],tail,id,1);
			s.insert(Div(getLine(tail,id),tail));
		}
		tail=id;
	}
	int qid;
	ll getS(int id,int x,int y)
	{
		if(!id) return -1;
		int xx=(int)p[id].p.x,yy=(int)p[id].p.y;
		if(yy-y<0) return -1;
		return 1ll*(x-xx)*(yy-y);
	}
	ll query(db Y)
	{
		int x=(int)X,y=(int)Y;
		auto it=s.lower_bound(Div(Line(0,Y),114514));
		if(it==s.end()) return getS(tail,x,y);
		return getS((*it).id,x,y);
	}
	void work(int ql,int qr,vector<int> &pid)
	{
		cnt=0;
		for(int i=ql;i<=qr;i++) p[++cnt]=data(Q[i],i);
		getp(pid);
		sort(p+1,p+cnt+1,[](data a,data b)
		{
			if(a.p.x==b.p.x) return a.id<0;
			return a.p.x<b.p.x;
		});
		tail=0;
		for(int i=1;i<=cnt;i++) pre[i]=nxt[i]=0;
		s.clear(),heap.clear();
		X=0;
		for(int i=1;i<=cnt;i++)
		{
			check(p[i].p.x);
			X=p[i].p.x;
			if(p[i].id<0) push(i);
			else qid=p[i].id,ans[p[i].id]=max(ans[p[i].id],query(p[i].p.y));
		}
	}
}

namespace Seg
{
	vector<int>pid[N<<2];
	void update(int k,int l,int r,int ql,int qr,int x)
	{
		if(ql<=l&&r<=qr)
		{
			pid[k].push_back(x);
			return;
		}
		int mid=(l+r)>>1;
		if(ql<=mid) update(k<<1,l,mid,ql,qr,x);
		if(qr>mid) update(k<<1|1,mid+1,r,ql,qr,x);
	}
	void solve(int k,int l,int r)
	{
		Solve::work(l,r,pid[k]);
		if(l==r)
		{
			printf("%lld\n",ans[l]);
			return;
		}
		int mid=(l+r)>>1;
		solve(k<<1,l,mid);
		solve(k<<1|1,mid+1,r);
	}
}

int main()
{
	static int st[N],ed[N];
//	freopen("range3.in","r",stdin);
//	freopen("range3_my.out","w",stdout);
	memset(ed,-1,sizeof(ed));
	m=read();
	for(int i=1;i<=m;i++)
	{
		int opt=read();
		if(opt==1)
		{
			int l=read(),r=read();
			S[++np]=Point(l,r),st[np]=nq+1;
		}
		if(opt==2) ed[read()]=nq;
		if(opt==3)
		{
			int l=read(),r=read();
			Q[++nq]=Point(l,r);
		}
	}
	for(int i=1;i<=np;i++)
	{
		if(ed[i]==-1) ed[i]=nq;
		if(st[i]<=ed[i]) Seg::update(1,1,nq,st[i],ed[i],i);
	}
	memset(ans,-1,sizeof(ans));
	Seg::solve(1,1,nq);
	return 0;
}
上一篇:3.服务器负载均衡的实现


下一篇:Python-冒泡排序