洛谷P3222 [HNOI2012]射箭(計算幾何,半平面交,雙端隊列)

洛谷題目傳送門html

設拋物線方程爲\(y=ax^2+bx(a<0,b>0)\),咱們想要求出一組\(a,b\)使得它儘量知足更多的要求。這個顯然能夠二分答案。c++

如何check當前的\(mid\)是否合法呢?每個限制條件形如\(y_{i_1}\le ax_i^2+bx_i\le y_{i_2}\),也就是\(\frac{y_{i_1}}{x_i}\le x_ia+b\le \frac{y_{i_2}}{x_i}\)。把\(a,b\)當作自變量,實際上每一個不等式就是一個半平面,咱們須要求出半平面交。算法

須要掌握向量、叉積等少許基礎算法(不過作到這題的大佬們確定會了),能夠參考xzy巨佬的總結spa

有一種\(O(n^2)\)的動態插入半平面的作法,能夠經過原題數據(目前最優解初版大部分是這種寫法),也能夠參考xzy巨佬的總結。code

蒟蒻構造了一組邊數不少的半平面交,能夠卡掉這種寫法,目前rank1的代碼本機須要20s以上。htm

一些hack數據能夠從這裏下(部分轉自liu_runda)blog

連接: https://pan.baidu.com/s/1Te0G-L2JrRu361qKAGorhQ排序

提取碼: ea9m隊列

談一談正經的\(O(n\log n)\)的實現吧。如下內容從蒟蒻的總結裏擓的。ip

咱們用有向直線(一個點和一個方向向量)表示半平面,如下默認半平面在有向直線的左側。

對有向直線按方向向量的極角排序,維護一個雙端隊列,存儲當前構成半平面的直線以及相鄰兩直線的交點。

每次加入一條有向直線,若是隊首/隊尾的交點在直線右側(用叉積判)則彈掉隊首/隊尾的直線。

爲何這樣是對的呢?由於加入直線的單調性,因此要被彈出的直線必定在隊首或隊尾。感興趣的話能夠本身手畫一些例子來理解。

須要注意的細節:

  1. 加入直線時,先彈隊尾,再彈隊首。
  2. 最後還要檢查隊尾交點是否在隊首直線的右側,若是是也要彈掉。
  3. 特判平行直線,在右側的要彈掉。
  4. 若是題目給出的半平面不必定有限制邊界,則應該手動加入一個INF邊界。

算法的複雜度瓶頸在排序,所以預先將這些有向直線排好序,二分check時忽略編號大於mid的直線就能夠了。時間複雜度\(O(n\log n)\)

注意這題的座標範圍是\(1e9\)範圍,所以INF設到\(1e10\)以上,EPS設到\(1e-10\)如下。

#include<bits/stdc++.h>
#define RG register
#define I inline
#define R RG int
#define G if(++ip==ie)if(fread(ip=buf,1,SZ,stdin))
using namespace std;
typedef double DB;
const int SZ=1<<19,N=2e5+9;
const DB INF=1e11,EPS=1e-11;
char buf[SZ],*ie=buf+SZ,*ip=ie-1;
inline int in(){
    G;while(*ip<'-')G;
    R x=*ip&15;G;
    while(*ip>'-'){x*=10;x+=*ip&15;G;}
    return x;
}
struct Vec{
    DB x,y;
    I Vec(){}
    I Vec(DB a,DB b){x=a;y=b;}
    I Vec operator+(Vec a){return Vec(x+a.x,y+a.y);}
    I Vec operator-(Vec a){return Vec(x-a.x,y-a.y);}
    I Vec operator*(DB a){return Vec(x*a,y*a);}//數乘
    I DB operator^(Vec a){return x*a.y-y*a.x;}//叉積
}k[N];
struct Line{
    Vec p,v;DB ang;int id;
    I Line(){}
    I Line(Vec a,Vec b,R c){p=a,v=b-a,ang=atan2(v.y,v.x),id=c;}
    I bool operator<(Line&a){return ang<a.ang;}
    I bool Right(Vec&a){return (v^(a-p))<-EPS;}
    I friend Vec Cross(Line&a,Line&b){//求直線交點
        return a.p+a.v*((b.v^(b.p-a.p))/(b.v^a.v));
    }
}a[N],q[N];
int p=0,l=1,r,mid;
bool HalfPlane(Line*a,Line*e){//求半平面是否有交
    R n=e-a,i=0,h=0,t=0;
    while(a[i].id>mid)++i;
    for(q[0]=a[i++];i<n;++i){
        if(a[i].id>mid)continue;
        while(h<t&&a[i].Right(k[t-1]))--t;
        while(h<t&&a[i].Right(k[h]))++h;
        if(a[i].ang!=q[t].ang)q[++t]=a[i];
        else if(a[i].Right(q[t].p))q[t]=a[i];
        if(h<t)k[t-1]=Cross(q[t-1],q[t]);
    }
    while(h<t&&q[h].Right(k[t-1]))--t;
    return t-h>1;
}
int main(){
    r=in();
    for(R i=1;i<=r;++i){
        DB x=in(),y1=in(),y2=in();
        a[++p]=Line(Vec(0,y1/x),Vec(1,y1/x-x),i);
        a[++p]=Line(Vec(1,y2/x-x),Vec(0,y2/x),i);
    }//邊界要設EPS不能設0,由於a、b爲0均不合題意
    a[++p]=Line(Vec(-INF,EPS),Vec(-EPS,EPS),0);
    a[++p]=Line(Vec(-EPS,EPS),Vec(-EPS,INF),0);
    a[++p]=Line(Vec(-EPS,INF),Vec(-INF,INF),0);
    a[++p]=Line(Vec(-INF,INF),Vec(-INF,EPS),0);
    sort(a+1,a+p+1);
    while(l<r){
        mid=(l+r+1)>>1;
        HalfPlane(a+1,a+p+1)?l=mid:r=mid-1;
    }
    cout<<l<<endl;
    return 0;
}
相關文章
相關標籤/搜索