您的位置:首页 > 其它

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

2016-05-30 22:14 507 查看
写在前面的话:

本题正解是凸包而不是二分,科学做法请右转各神犇blog。以下是蒟蒻的二分做法。

我的代码在不开o2的情况下大约800ms-900ms一个点 开o2大约300ms-400ms一点。

首先设k=ab,记xi=atki,yi=dnfi

则对于每个1≤i≤n,战斗力为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
,y
,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);
}


.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  二分 乱搞