BJ模拟 Goodness(凸包+三分答案)

解决一个关于卡牌好感度计算的问题,通过构建凸包模型来寻找两张卡牌间好感度差异最大的组合。

Description

       桌面上放有 n 张卡牌。对于每张卡牌,一面是绿色的,另一面是红色的。卡牌的每一面都标有一个整数。对于卡牌a和卡牌b,卡牌a对卡牌b的好感度为卡牌a绿色面的数与卡牌b红色面的数的乘积。

       举个例子,如果卡牌a绿色面标有10,红色面标有3;卡牌b绿色面标有7,红色面标有-2。那么a对b的好感度为 10×(2)=20 ,b对a的好感度为 7×3=21 。则a和b的好感度的差异为 |21(20)|=41 。

       现在,你知道这 n 张卡牌每一面的数,请你找出两张卡牌,使得他们好感度的差异最大。

Input

       第一行为一个整数 n ,表示卡牌的数量。

       接下来n行,每行两个整数 gi,ri ,分别表示第i张卡牌绿色面和红色面的数。

Output

        输出一个整数:好感度差异的最大值。


题解:凸包。

首先,把(g[i], r[i])看成平面上的点,所求的值即为三角形(原点、a、b)面积的最大值的两倍(a, b为卡牌)。

其次,选出的两张卡牌一定是凸包上的点。下面证明:

假设两张卡牌都不是凸包上的点,则任选这两点中的一点a与原点O连成直线。则现在的目标是选择另外一点b使得三角形Oab的面积最大。将所有点往直线Oa作高发现,与直线Oa距离最远的点一定是凸包上的点,与假设矛盾。

假设一张卡牌a是凸包上的点,另一张卡牌b不是,仍可按照上面的方法证明b一定是凸包上的点,从而使假设矛盾。

如果数据是随机的话,把凸包上的点找出来(大概也就40个左右),O(n^2)枚举一下即可。


但是后面60%的数据都是构造的,O(n^2)可能过不了(不排除有些同学水得比较高超把它们都水过了)~

我们可以枚举凸包上的一个点a,通过二分/三分找到离直线Oa最远的点。

本题标程时间复杂度O(n log n)


50分凸包暴力代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int Maxn=2e5+50;
inline ll read()
{
	char ch=getchar();ll i=0,f=1;
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){i=(i<<3)+(i<<1)+ch-'0';ch=getchar();}
	return i*f;
}
ll a[Maxn],b[Maxn];
int n,cnt,n2;
ll maxx;
struct point
{
	ll x,y;
	point(){}
	point(ll x,ll y):x(x),y(y){}
	friend inline point operator -(const point &a,const point &b)
	{
		return point(a.x-b.x,a.y-b.y);
	}
	friend inline point operator +(const point &a,const point &b)
	{
		return point(a.x+b.x,a.y+b.y);
	}
	friend inline ll operator *(const point &a,const point &b)
	{
		return a.x*b.y-a.y*b.x;
	}
	inline ll norm()
	{
		return x*x+y*y;
	}
}p[Maxn],q[Maxn];
inline bool compare_p(const point &a,const point &b)
{
	ll det=(a-p[1])*(b-p[1]);
	if(det!=0)return det>0;
	return (a-p[1]).norm()<(b-p[1]).norm();
}
inline void Graham()
{
	int id=1;
	for(int i=2;i<=n;i++)
	{
		if(p[i].x<p[id].x||(p[i].x==p[i].x&&p[i].y<p[id].y))id=i;
	}
	if(id!=1)swap(p[1],p[id]);
	sort(p+2,p+n+1,compare_p);
	q[++cnt]=p[1];
	for(int i=2;i<=n;i++)
	{
		while(cnt>=2&&(p[i]-q[cnt-1])*(q[cnt]-q[cnt-1])>=0)--cnt;
		q[++cnt]=p[i];
	}
}
inline int nxt(int i)
{
	if(i!=cnt)return i+1;
	return 1;
}
inline void update(int now)
{
	if(n2<=5000)
	{
		for(int j=1,i=nxt(now);j<=n2;j++)
	        {
			ll val=abs(q[i]*q[now]);
			if(val>maxx)maxx=val;
			i=nxt(i);
	        }
	}
	else
	{
		int num=10000*7000/cnt;
		int i=now+cnt/2-num;
		if(i>cnt)i-=cnt;
		if(i<=0)i+=cnt;
		for(int j=1;j<=num;j++)
		{
			ll val=abs(q[i]*q[now]);
		if(val>maxx)maxx=val;
		i=nxt(i);
		}
	}
}
int main()
{
	n=read();
	if(n<=3000)
	{
		for(int i=1;i<=n;i++)a[i]=read(),b[i]=read();
		for(int i=1;i<n;i++)
			for(int j=i+1;j<=n;j++)
			{
				maxx=max(maxx,abs(a[i]*b[j]-a[j]*b[i]));
			}
	}
	else
	{
		for(int i=1;i<=n;i++)p[i].x=read(),p[i].y=read();
		Graham();
		n2=cnt/2+2;
		for(int i=1;i<=n;i++)
		{
			update(i);
		}
	}
	printf("%lld\n",maxx);
	return 0;
}


100分凸包+三分代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int Maxn=2e5+50;
inline ll read()
{
	char ch=getchar();ll i=0,f=1;
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){i=(i<<3)+(i<<1)+ch-'0';ch=getchar();}
	return i*f;
}
ll a[Maxn],b[Maxn];
int n,cnt,n2;
ll maxx;
struct point
{
	ll x,y;
	point(){}
	point(ll x,ll y):x(x),y(y){}
	friend inline point operator -(const point &a,const point &b)
	{
		return point(a.x-b.x,a.y-b.y);
	}
	friend inline point operator +(const point &a,const point &b)
	{
		return point(a.x+b.x,a.y+b.y);
	}
	friend inline ll operator *(const point &a,const point &b)
	{
		return a.x*b.y-a.y*b.x;
	}
	inline ll norm()
	{
		return x*x+y*y;
	}
}p[Maxn],q[Maxn];
inline bool compare_p(const point &a,const point &b)
{
	ll det=(a-p[1])*(b-p[1]);
	if(det!=0)return det>0;
	return (a-p[1]).norm()<(b-p[1]).norm();
}
inline void Graham()
{
	int id=1;
	for(int i=2;i<=n;i++)
	{
		if(p[i].x<p[id].x||(p[i].x==p[i].x&&p[i].y<p[id].y))id=i;
	}
	if(id!=1)swap(p[1],p[id]);
	sort(p+2,p+n+1,compare_p);
	q[++cnt]=p[1];
	for(int i=2;i<=n;i++)
	{
		while(cnt>=2&&(p[i]-q[cnt-1])*(q[cnt]-q[cnt-1])>=0)--cnt;
		q[++cnt]=p[i];
	}
}
inline ll calc(int p1,int p2)
{
	return abs(q[p1]*q[p2]);
}
inline int findr(int now)
{
	int l=now+1,r=now+cnt-1;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(q[now]*q[mid]>0)l=mid+1;
		else r=mid-1;
	}
	return l-1;
}
inline point findh1(int now,int l,int r)
{
	for(int i=1;i<=200&&l!=r;i++)
	{
		int f1=l+(r-l)/3;
		int f2=r-(r-l)/3;
		if(calc(f1,now)<=calc(f2,now))l=f1+1;
		else r=f2;
	}
	return q[l];
}
inline point findh2(int now,int l,int r)
{
	for(int i=1;i<=200&&l!=r;i++)
	{
		int f1=l+(r-l)/3;
		int f2=r-(r-l)/3;
		if(calc(f1,now)<calc(f2,now))l=f1+1;
		else r=f2;
	}
	return q[l];
}
inline void update(int now)
{
	int l1=now+1,r1=findr(now),l2=r1+1,r2=now+cnt-1;
	//找到凸包上临界点 两侧分别进行三分答案 
	point p1=findh1(now,l1,r1);
	point p2=findh2(now,l2,r2);
	ll max1=abs(q[now]*p1),max2=abs(q[now]*p2);
	maxx=max(maxx,max(max1,max2));
}
int main()
{
	n=read();
	if(n<=6000)
	{
		for(int i=1;i<=n;i++)a[i]=read(),b[i]=read();
		for(int i=1;i<n;i++)
			for(int j=i+1;j<=n;j++)
			{
				maxx=max(maxx,abs(a[i]*b[j]-a[j]*b[i]));
			}
	}
	else
	{
		for(int i=1;i<=n;i++)p[i].x=read(),p[i].y=read();
		Graham();
		memcpy(q+cnt+1,q+1,sizeof(point)*cnt);
		for(int i=1;i<=n;i++)
		{
			update(i);
		}
	}
	printf("%lld\n",maxx);
	return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值