hdu4445 三分+二分+区间覆盖

来源:互联网 发布:UC浏览器mini java版 编辑:程序博客网 时间:2024/06/06 15:45


Crazy Tank

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 5198    Accepted Submission(s): 1055


Problem Description
Crazy Tank was a famous game about ten years ago. Every child liked it. Time flies, children grow up, but the memory of happy childhood will never go.

Now you’re controlling the tank Laotu on a platform which is H meters above the ground. Laotu is so old that you can only choose a shoot angle(all the angle is available) before game start and then any adjusting is not allowed. You need to launch N cannonballs and you know that the i-th cannonball’s initial speed is Vi.
On the right side of Laotu There is an enemy tank on the ground with coordination(L1, R1) and a friendly tank with coordination(L2, R2). A cannonball is considered hitting enemy tank if it lands on the ground between [L1,R1] (two ends are included). As the same reason, it will be considered hitting friendly tank if it lands between [L2, R2]. Laotu's horizontal coordination is 0.
The goal of the game is to maximize the number of cannonballs which hit the enemy tank under the condition that no cannonball hits friendly tank.
The g equals to 9.8.
 


Input
There are multiple test case.
Each test case contains 3 lines.
The first line contains an integer N(0≤N≤200), indicating the number of cannonballs to be launched.
The second line contains 5 float number H(1≤H≤100000), L1, R1(0<L1<R1<100000) and L2, R2(0<L2<R2<100000). Indicating the height of the platform, the enemy tank coordinate and the friendly tank coordinate. Two tanks may overlap.
The third line contains N float number. The i-th number indicates the initial speed of i-th cannonball.
The input ends with N=0.
 


Output
For each test case, you should output an integer in a single line which indicates the max number of cannonballs hit the enemy tank under the condition that no cannonball hits friendly tank.
 


Sample Input
210 10 15 30 3510.020.0210 35 40 2 3010.020.00
 


Sample Output
10
Hint
In the first case one of the best choices is that shoot the cannonballs parallelly to the horizontal line, then the first cannonball lands on 14.3 and the second lands on 28.6.In the second there is no shoot angle to make any cannonball land between [35,40] on the condition that no cannonball lands between [2,30].
 


Source
2012 Asia JinHua Regional Contest
 


这道题虽然有简单的方法水过,就是将PI分成有限份来计算,取个最大值即可,只要分得够细,一般就可以过,因为最佳角度一般会有很多,而且一般是连续的,因此分的这些小段很容易被答案覆盖。

但出题者肯定不是这样想的,因为上面的做法从理论上来讲是不正确的。正确的做法是三分+二分+区间重叠。

首先我们可以推出射程与速度、夹角之间的关系,而且这个函数是凸性的(尽管我没有从理论上证明,求出二阶导数却无法证明恒小于0或者恒大于0),但从物理角度分析,应该是先单调增再单调减,而且如果用数学软件画个函数图像的话,应该是显而易见的。

这里假设就是凸性函数,那么必然存在一个最大值,我们用三分求得最大值后,将函数图像分成左右两部分,由于左侧部分单调增,右侧部分单调减,那么我们可以利用二分来求得某个射程对应的角度,再根据单调性就可以求的区间了。

这里注意一下:由于题目交代,包含区间端点,为了简化题目,我们将区间左端点加上eps,右端点减去eps,这样做仍然是可以求得正确答案的,因为连续性,但是这样就避免了讨论开区间的问题,全部转化成了闭区间。

接下来就是区间重叠问题,一般想法是线段树吧,但是如果离散化+线段树,那这题简直做不下去,但是记得有个+1,-1的简单方法,我们将区间端点从小到大排序,当扫描到左端点则+1,表示之后覆盖次数+1,直到遇到右端点,覆盖次数-1。

这里还是有些细节问题,这里包含了好友区间,因此我们必须保证扫描到好友区间时不能成为正确答案,因此左端点-inf,然后遇到右端点再加回来。还有一个地方,就是在排序时,如果角度相同的情况,我们应该先算右端点,再算左端点,因为如果先算左端点,还没来得及减去右端点,则已更新最大值,这样显然最大值偏大,但是对于好友区间,则应该先算左端点,如果先算右端点则可能多次+inf,这样正确答案就变成无穷大了,总之就是先算减法,再算加法,于是我们只要对比较大小方式限制一下即可。


代码:

#include<iostream>#include<cstdio>#include<cstring>#include<cmath>#include<vector>#include<algorithm>using namespace std;const double PI=acos(-1.0);const double eps=1e-8;const int inf=10010;int sgn(double x){    if(fabs(x)<eps) return 0;    return x>0?1:-1;}struct status{    double pos;    int val;    status(){}    status(double p,int v):pos(p),val(v){}    bool operator<(const status &x)const{        return sgn(pos-x.pos)<0||sgn(pos-x.pos)==0&&val<x.val;    }};vector<status> dv;double v[210];double h,maxangle,maxdis;double dis(double angle,double velocity){ //由角度和速度计算水平射程    return (-velocity*cos(angle)+            sqrt(velocity*velocity*cos(angle)*cos(angle)+2*9.8*h))*velocity*sin(angle)/9.8;}double tri(double velocity){ //三分角度求最大射程    double l=0,r=PI;    while(fabs(r-l)>eps){        double lm=(l+r)/2;        double rm=(lm+r)/2;        if(dis(lm,velocity)<dis(rm,velocity)) l=lm;        else r=rm;    }    return l;}double binaryl(double axis,double velocity){ //左侧二分    double l=0,r=maxangle;    while(fabs(r-l)>eps){        double m=(l+r)/2;        if(dis(m,velocity)>axis) r=m;        else l=m;    }    return l;}double binaryr(double axis,double velocity){ //右侧二分    double l=maxangle,r=PI;    while(fabs(r-l)>eps){        double m=(l+r)/2;        if(dis(m,velocity)>axis) l=m;        else r=m;    }    return l;}int main(){    int n;    double a,b,c,d;    while(cin>>n,n){        cin>>h>>a>>b>>c>>d;        a+=eps,b-=eps,c+=eps,d-=eps;        for(int i=0;i<n;i++)            cin>>v[i];        dv.clear();        for(int i=0;i<n;i++){            maxangle=tri(v[i]);            maxdis=dis(maxangle,v[i]);            //敌人区间左端点+1,右端点-1            if(sgn(a-maxdis)<=0){                dv.push_back(status(binaryl(a,v[i]),1));                dv.push_back(status(binaryr(a,v[i]),-1));                if(sgn(b-maxdis)<=0){ //a,b<maxangle,两段区间                    dv.push_back(status(binaryl(b,v[i]),-1));                    dv.push_back(status(binaryr(b,v[i]),1));                }            }            //好友区间左端点-inf,右端点+inf            if(sgn(c-maxdis)<=0){                dv.push_back(status(binaryl(c,v[i]),-inf));                dv.push_back(status(binaryr(c,v[i]),inf));                if(sgn(d-maxdis)<=0){ //c,d<maxangle,两段区间                    dv.push_back(status(binaryl(d,v[i]),inf));                    dv.push_back(status(binaryr(d,v[i]),-inf));                }            }        }        sort(dv.begin(),dv.end());        int ans=0,res=0;        for(int i=0;i<dv.size();i++){            ans+=dv[i].val;            res=max(res,ans); //求前缀和最大值        }        cout<<res<<endl;    }    return 0;}



0 0
原创粉丝点击