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

博客内容介绍了如何解决BZOJ 4570题目中的妖怪战斗力计算问题。通过将攻击力和防御力转换为平面上的点,并利用上凸壳算法找到妖怪战斗力的最小值。文章详细解释了如何利用均值不等式确定最佳战斗力,并提供了求解最大战斗力的思路,涉及到排序和上凸壳遍历的过程。

传送门: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+ykx1ky x + y − k x − 1 k y 即为这条直线在 x,y x , y 轴上的截距的绝对值之和。所以只需要维护点的一个上凸壳(同样斜率的线,在凸壳上的点最先被扫到)。

再考虑求妖怪的最大战斗力,假设先将点按 x x 升序排序,若当前点(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个点有最强战斗力的斜率并结合满足其最小的斜率 yx − y x 来更新答案即可。


代码

#include<bits/stdc++.h>
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 && 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.y<A.y:A.x<B.x;}

inline 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;i<tot;++i){
        k=minslope(a[i]);ka=slope(a[i-1],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);
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值