6217. 【NOI2019模拟2019.6.14】最大面积

有一堆向量\(A_i\),多次询问向量\(P\),查\(\max_{L\le R}P\times \sum_{i=L}^R A_i\)

\(n\le 10^5\)


设向量\(P=(A,B)\),区间和为\((X,Y)\)。需要最大化\(F=AY-BX\)。于是\(Y=\frac{B}{A}X+\frac{F}{A}\)。以\(A>0\)为例,相当于在\((X,Y)\)引一条斜率\(\frac{B}{A}\)的直线,求所有这样的\((X,Y)\)引出的直线的最大截距。

于是答案一定在所有\((X,Y)\)组成的凸包上。如果能求出这个凸包,就可以直接在上凸壳上二分在\(O(\lg)\)的时间内得到答案。

以下是构建这个凸包的方式:分治构造,求出\([L,mid],[mid+1,R]\)内的凸包,以及跨过中点的。对于跨过中点的,对左区间做后缀和,对右区间做前缀和,两者闵科夫斯基和。然后把得到的这三个凸包合并起来。

因为一个分治区间最多贡献区间长度的点数,总点数不超过区间总长,即\(O(n\lg n)\)。因为计算跨过中点时还需要求凸包,时间\(O(n\lg^2n)\)

(诶所以我的实现方式是\(O(n\lg^3 n)\)?)

upd:
其实不需要每次合并左右区间及跨过中点的凸包,直接在最后把所有凸包合并即可。这样做时间是\(O(n\lg^2 n)\)
计算跨过中点贡献的时候,应该可以维护好横坐标的顺序,然后归并,这部分时间\(O(n\lg n)\)。但是合并凸包时间还是这么多(如果在分治的时候合并,虽然可以做到线性,但是由于一层点数是\(O(len\lg len)\),总共还是\(O(n\lg^2 n)\)的时间。)


第一次写凸包(平常只是维护个凸壳)+第一次写闵科夫斯基和。

其实凸包可以直接分别维护上凸壳和下凸壳,应该更好写,也方便优化(在已经排好序的情况下,凸包合并应该可以线性做)

这次用极角排序的方式写凸包,有点细节。

求凸包和闵科夫斯基和的时候都需要找到最左端(坐标相同就最下端)的点作为基准点,从它开始跑,因为这样保证了这个点一定在凸包上。

然后凸包合并的时候我重新求了一次凸包,所以时间多了一个\(O(\lg)\)

using namespace std;
#include <bits/stdc++.h>
#define N 100005
#define ll long long
#define db long double
int n,Q;
struct DOT{
	ll x,y;
	DOT(ll _x=0,ll _y=0){x=_x,y=_y;}
	db len2(){return (db)x*x+(db)y*y;}
} d[N];
bool operator<(DOT a,DOT b){return a.x<b.x || a.x==b.x && a.y<b.y;}
DOT operator+(DOT a,DOT b){return (DOT){a.x+b.x,a.y+b.y};}
DOT operator-(DOT a,DOT b){return (DOT){a.x-b.x,a.y-b.y};}
db cro(DOT a,DOT b){return (db)a.x*b.y-(db)a.y*b.x;}
vector<DOT> q[N*4],u,v;
vector<DOT> qa;
bool cmparc(DOT a,DOT b){return cro(a,b)>0 || cro(a,b)==0 && a.len2()<b.len2();}
void getcon(vector<DOT> &w){
	DOT O=w[0];
	for (int i=1;i<w.size();++i)
		O=min(O,w[i]);
	qa.clear();
	for (int i=0;i<w.size();++i)
		if (w[i].x!=O.x || w[i].y!=O.y)
			qa.push_back(w[i]-O);
	sort(qa.begin(),qa.end(),cmparc);
	static DOT st[N*20];
	int tp;
	st[tp=1]=DOT(0,0);
	for (int i=0;i<qa.size();++i){
		while (tp>1 && cro(st[tp]-st[tp-1],qa[i]-st[tp-1])<=0)
			--tp;
		st[++tp]=qa[i];
	}
	while (tp>1 && cro(st[tp]-st[tp-1],DOT(0,0)-st[tp-1])<0)
		--tp;
	w.clear();
	for (int i=1;i<=tp;++i)
		w.push_back(st[i]+O);
}
void getsum(vector<DOT> &w){
	DOT O=u[0]+v[0];
	u.push_back(u[0]);
	v.push_back(v[0]);
	w.clear();
	w.push_back(O);
	int i=0,j=0;
	while (i<u.size()-1 || j<v.size()-1)
		if (i<u.size()-1 && (j==v.size()-1 || cro(u[i+1]-u[i],v[j+1]-v[j])>0))
			w.push_back(O=O+u[i+1]-u[i]),i++;
		else
			w.push_back(O=O+v[j+1]-v[j]),j++;
	w.pop_back();
}
void merge(vector<DOT> &a,vector<DOT> &b){
	for (int i=0;i<b.size();++i)
		a.push_back(b[i]);
	b.clear();
}
void divide(int k,int l,int r){
	if (l==r){
		q[k].push_back(d[l]);
		return;
	}
	int mid=l+r>>1;
	divide(k<<1,l,mid);
	divide(k<<1|1,mid+1,r);
	u.clear(),v.clear();
	DOT s(0,0);
	for (int i=mid;i>=l;--i)
		s=s+d[i],u.push_back(s);
	s=DOT(0,0);
	for (int i=mid+1;i<=r;++i)
		s=s+d[i],v.push_back(s);
	getcon(u);
	getcon(v);
	getsum(q[k]);
	merge(q[k],q[k<<1]);
	merge(q[k],q[k<<1|1]);
	getcon(q[k]);
}
vector<DOT> f,g;
ll ask(vector<DOT> &h,DOT p){
	ll ans=0;
	for (int i=0;i<h.size();++i){
		ans=max(ans,(ll)cro(p,h[i]));
	}
	return ans;
	int l=0,r=(int)h.size()-2,res=h.size()-1;
	while (l<=r){
		int mid=l+r>>1;
		if (cro(p,h[mid+1]-h[mid])<=0)
			r=(res=mid)-1;
		else
			l=mid+1;
	}
	return cro(p,h[res]);
}
int main(){
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
//	freopen("area.in","r",stdin);
//	freopen("area.out","w",stdout);
	scanf("%d%d",&n,&Q);
	for (int i=1;i<=n;++i)
		scanf("%lld%lld",&d[i].x,&d[i].y);
	divide(1,1,n);
	DOT lef=q[1][0],rig=q[1][0];
	int pos=0;
	for (int i=1;i<q[1].size();++i)
		if (rig<q[1][i])
			rig=q[1][i],pos=i;
	for (int i=0;i<=pos;++i) f.push_back(DOT(q[1][i].x,-q[1][i].y));
	g.push_back(lef);
	for (int i=q[1].size()-1;i>=pos;--i) g.push_back(q[1][i]);
	while (Q--){
		ll A,B;
		scanf("%lld%lld",&A,&B);
		ll ans=0;
		if (A==0){
			if (B==0)
				ans=0;
			else
				ans=max(-B*lef.x,-B*rig.x);
		}
		else if (A>0)
			ans=ask(g,DOT(A,B));
		else
			ans=ask(f,DOT(-A,B));
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2021-05-07 07:39  jz_597  阅读(54)  评论(0编辑  收藏  举报