【BZOJ】4570: [Scoi2016]妖怪-上凸壳

传送门:bzoj4570


题解

将妖怪的攻击力,防御力分别设为 x,y x , y

一开始把战斗力理解成 max(ba×x+y,ab×y+x) m a x ( b a × x + y , a b × y + x ) 了。实际上为 x+y+bax+aby x + y + b a x + a b y 。(懵逼了半天,雾)

为方便表示,首先设 k=ba k = − b a ( a,b a , b 均为正实数, k<0 k < 0 )。则每个妖怪的战斗力为 x+ykx1ky x + y − k x − 1 k y ,对于单个妖怪,由均值不等式得到当 k=yx k = − y x 时,战斗力最小。

可以将 x,y x , y 转化为平面上的点 (x,y) ( x , y ) 考虑, k k 即为过点 (x,y) ( x , y ) 的一条直线的斜率,而 x+ykx1ky x + y − k x − 1 k y 即为这条直线在 x,y x , y 轴上的截距的绝对值之和。所以只需要维护点的一个上凸壳(同样斜率的线,在凸壳上的点最先被扫到)。

再考虑求妖怪的最大战斗力,假设先将点按 x x 升序排序,若当前点 (x,y) ( x , y ) 战斗力大于上一个点 (x,y) ( x ′ , y ′ ) ,则有不等式:

x+ykx1ky>x+ykx1ky x + y − k x − 1 k y > x ′ + y ′ − k x ′ − 1 k y ′

化简得到:
k<yyxx k < y − y ′ x − x ′

显然也是上凸壳的形式。那么直接求出上凸壳,在凸壳上按 x x 从左到右遍历满足第 i i 个点有最强战斗力的斜率并结合满足其最小的斜率 yx − y x 来更新答案即可。


代码

#include
using namespace std;
typedef double db;
const int N=1e6+100;
const db eps=1e-8,inf=1e12;
int n,tot,cnt;db k,ka,kb,ans;
struct P{db x,y;}t[N],a[N];
inline P operator - (const P&A,const P&B){return (P){A.x-B.x,A.y-B.y};}
inline db cg(P A,P B){return A.x*B.y-A.y*B.x;}

inline int rd()
{
    char ch=getchar();int x=0,f=1;
    while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
    return x*f;
}
inline int dcmp(db x)
{
    if(x-eps) return 0;
    return x>0 ? 1:-1;
}

inline db minslope(P A){return -(sqrt((db)A.y/(db)A.x));}//满足值最小的斜率

inline bool cmp(const P&A,const P&B)
{return dcmp(A.x-B.x)==0? B.yinline db slope(P A,P B)
{
    if(dcmp(A.x)==0 && dcmp(A.y)==0) return inf;
    if(dcmp(B.x)==0 && dcmp(B.y)==0) return -inf;
    if(dcmp(A.x-B.x)==0) return inf;
    return (A.y-B.y)/(A.x-B.x);
}

inline db get(P p,db k){if(k>=0) return inf;return p.x+p.y-k*p.x-p.y/k;}
//更新答案 

int main(){
    int i,j;
    n=rd();ans=inf;
    for(i=1;i<=n;++i) scanf("%lf%lf",&t[i].x,&t[i].y);
    if(n==1) {printf("%.4lf\n",get(t[1],minslope(t[1])));return 0;}
    sort(t+1,t+n+1,cmp);
    a[++tot]=t[1];
    for(i=2;i<=n;++i){
        while(tot>1 && dcmp(cg(a[tot]-a[tot-1],t[i]-a[tot]))>=0) tot--;
        a[++tot]=t[i];
    }//求凸包 
    ka=slope(a[1],a[2]);k=minslope(a[1]);
    if(k>=ka) ans=min(ans,get(a[1],k));else ans=min(ans,get(a[1],ka)); 
    kb=slope(a[tot-1],a[tot]);k=minslope(a[tot]);
    if(k<=kb) ans=min(ans,get(a[tot],k));else ans=min(ans,get(a[tot],kb));
    //初始化起点和终点 
    for(i=2;i1],a[i]);kb=slope(a[i],a[i+1]);
        if(dcmp(k-ka)<=0 && dcmp(k-kb)>=0) ans=min(ans,get(a[i],k));//判断最小值是否在范围[ka,kb]内 
        else{ans=min(ans,get(a[i],ka));ans=min(ans,get(a[i],kb));}//否则双钩函数的边界一定是最优的 
    } 
    printf("%.4lf\n",ans);
}

你可能感兴趣的:(凸包)