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;
}