[bzoj4570][scoi2016]妖怪 二分区间

写在前面的话:
本题正解是凸包而不是二分,科学做法请右转各神犇blog。以下是蒟蒻的二分做法。
我的代码在不开o2的情况下大约800ms-900ms一个点 开o2大约300ms-400ms一点。

首先设 k=ab ,记 xi=atki,yi=dnfi
则对于每个 1in ,战斗力为 fk(i)=(1k+1)xi+(k+1)yi
发现这个是对勾函数,我们可以利用这个做很多事情。(就是卡常辣)

然后我们要二分的是k 不是二分答案!

然后我们要二分的是k 不是二分答案!

然后我们要二分的是k 不是二分答案!


虽然看上去解并不随k单调或者有唯一极值,也找不到二分的方向,但是我们可以采取这样的策略:
1.二分时记录当前最优ans ,上次枚举的last_k和妖怪last_i,当我们枚举到一个 k0 时,记录当前得到最大的f(i)的妖怪为maxi。
2.如果答案比之前更优,那么直接更新last_ans,last_k和last_i,下一次二分往maxi的那条对勾函数更低的方向走。往反方向走答案肯定不会更优。(ps:这时已经知道最后答案肯定在 f(maxi)和 maxi对勾函数最低点 之间,或许可以区间-答案交替二分?好像很炫酷)
3.如果答案并不比之前优,下一次二分往last_k的方向走。注意此时什么都不更新。
4.注意实现时用last_dir代替last_k和last_i记录,因为last_k只用到它的方向。还有注意下图this_k就相当于当前二分l和r的mid

2的正确性显然,3的正确性…并不是那么显然。你可以脑补出这样一幅图:

根据3 在this_k时答案没有更优,往右走,就会错过了左边更优的答案?
事实上上图情况并不会发生,因为对勾函数(在第一象限)是单峰的,并且严格先递减后递增
图大概是这样,由对勾函数性质可以推出虚线部分,就不写数学证明了。
这里写图片描述
当last_k在this_k左边时同理,当maxi=last_i时显然。

顺便一提还有一个小优化:因为题中对勾函数最低点可以 O(1) 算,最开始二分时就不用l=eps,r=inf了,可以l=所有对勾函数最左边最低点,r=所有对勾函数最右边最低点。(我语文弱啊还是看代码吧)

#include <cstdio>
#include <cmath>
//#include <ctime>
#define N 1000005
#define INF (10000000000000ll)
#define eps 1e-8
#define Min(a,b) (a<b?a:b)
#define Max(a,b) (a>b?a:b)
inline int RD()
{
    static int res;
    static char cr;
    while( (cr=getchar())<'0' || cr>'9');
    res=cr-'0';
    while( (cr=getchar())>='0' && cr<='9')
        res=(res<<1)+(res<<3)+cr-'0';
    return res;
}
int x[N],y[N],n;
double ans;
int maxi;
double check(double k)
{
    double tox=1/k+1,toy=k+1;
    double res=0,r;
    for(int i=1;i<=n;i++)
    {
        r=tox*x[i]+toy*y[i];
//      if(r>ans) return -1;//实测这个没用..
        if(r>res)
            maxi=i,res=r;
    }
    return res;
}
int  main()
{
//  int c1=clock();
//  freopen("monster.in","r",stdin);
    n=RD();
    double l=INF,r=eps,temp;
    for(int i=1;i<=n;i++)
    {
        x[i]=RD();y[i]=RD();
        temp=sqrt((double)x[i]/y[i]);
        l=Min(l,temp);
        r=Max(r,temp);
    }
    double mid,res; ans=INF;
    int last_dir=-1;
    int lim=42;//保证正确性的情况下,卡时//=42000000/n;
    //二分42次是拍出来的.什么泥问我拿什么对拍?lim=100就好了
    while(lim--)
    {
        mid=(l+r)/2;
        res=check(mid);
        if(res>ans)
            last_dir? l=mid: r=mid;
        else//res<ans
        {
            ans=res;
            temp=sqrt((double)x[maxi]/y[maxi]);
            mid<temp? l=mid: r=mid;
            last_dir=(mid>temp);
        }
    }
    printf("%.04lf",ans);   
//  printf("\ntime %d",clock()-c1);
}

.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值