【bzoj2732】[HNOI2012]射箭 二分+半平面交
題目描述
沫沫最近在玩一個二維的射箭遊戲,如下圖 1 所示,這個遊戲中的 x 軸在地面,第一象限中有一些豎直線段作為靶子,任意兩個靶子都沒有公共部分,也不會接觸坐標軸。沫沫控制一個位於(0,0)的弓箭手,可以朝 0 至 90?中的任意角度(不包括 0度和 90度),以任意大小的力量射出帶有穿透能力的光之箭。由於遊戲中沒有空氣阻力,並且光之箭沒有箭身,箭的軌跡會是一條標準的拋物線,被軌跡穿過的所有靶子都認為被沫沫射中了,包括那些 只有端點被射中的靶子。這個遊戲有多種模式,其中沫沫最喜歡的是闖關模式。在闖關模式中,第一關只有一個靶 子,射中這個靶子即可進入第二關,這時在第一關的基礎上會出現另外一個靶子,若能夠一箭 雙雕射中這兩個靶子便可進入第三關,這時會出現第三個靶子。依此類推,每過一關都會新出 現一個靶子,在第 K 關必須一箭射中前 K 關出現的所有 K 個靶子才能進入第 K+1 關,否則遊戲 結束。沫沫花了很多時間在這個遊戲上,卻最多只能玩到第七關“七星連珠”,這讓她非常困惑。 於是她設法獲得了每一關出現的靶子的位置,想讓你告訴她,最多能通過多少關
輸入
輸入文件第一行是一個正整數N,表示一共有N關。接下來有N行,第i+1行是用空格隔開的三個正整數xi,yi1,yi2(yi1<yi2 ),表示第i關出現的靶子的橫坐標是xi,縱坐標的範圍是從yi1到yi2 。
輸入保證30%的數據滿足N≤100,50%的數據滿足N≤5000,100%的數據滿足N≤100000且給 出的所有坐標不超過109 。
輸出
僅包含一個整數,表示最多的通關數。
樣例輸入
5
2 8 12
5 4 5
3 8 10
6 2 3
1 3 7
樣例輸出
3
題解
二分+半平面交
題目一眼二分答案(別問我怎麽看出的。。做題的經驗告訴的我),然後轉化為判定性問題。
那麽如何判斷能否滿足條件?考慮每個限制條件:$\begin{cases}ax_i^2+bx_i\ge y_i\\ax_i^2+bx_i\le z_i\end{cases}$。
這裏面的ab是變量,那麽可以將限制條件看做是aOb平面直角坐標系上的幾何限制條件,不等式有解<=>限制的條件有公共部分。
所以將限制條件轉化為半平面求交即可。
真簡單= =
本體最惡心之處在於細節、細節、細節。。。只有細節。。。
細節1:a必須小於0,b必須大於0,因此需要將半平面交限制在第二象限,所以需要添加兩條輔助線。。。
細節3:eps這個東西是真的煩人。。。本題的eps應設為1e-18,且只在處理限制條件時加(因為等於的時候也算滿足條件)和限制象限時加(因為不能等於),其余時候不能加。並且需要使用long double。
細節4:本題卡常。。。所以所有小函數必須加inline不然會GG。。。排序是需要先把極角預處理出來,不能在比較時現求,否則也會GG。。。
直接上代碼吧:
#include <cmath> #include <cstdio> #include <cstring> #include <algorithm> #define N 200010 #define eps 1e-18 using namespace std; typedef long double ld; struct point { ld x , y; point() {} point(ld a , ld b) {x = a , y = b;} point operator+(const point &a)const {return point(x + a.x , y + a.y);} point operator-(const point &a)const {return point(x - a.x , y - a.y);} point operator*(const ld &a)const {return point(x * a , y * a);} }p[N]; struct line { point p , v; ld ang; line() {} line(ld a , ld b , ld c , ld d) {p = point(a , b) , v = point(c , d) , ang = atan2(v.y , v.x);} }a[N] , q[N]; ld px[N] , py[N] , pz[N]; inline ld cross(point a , point b) {return a.x * b.y - a.y * b.x;} inline bool left(line a , point b) {return cross(a.v , b - a.p) > 0;} inline point inter(line a , line b) { point u = a.p - b.p; ld tmp = cross(b.v , u) / cross(a.v , b.v); return a.p + a.v * tmp; } bool cmp(const line &a , const line &b) { return a.ang == b.ang ? left(a , b.p) : a.ang < b.ang; } bool judge(int mid) { int i , tot = 1 , l = 1 , r = 1; for(i = 1 ; i <= mid ; i ++ ) a[i] = line(0 , py[i] / px[i] - eps , -1 , px[i]) , a[i + mid] = line(0 , pz[i] / px[i] + eps , 1 , -px[i]); a[mid * 2 + 1] = line(-eps , eps , 0 , -1) , a[mid * 2 + 2] = line(-eps , eps , -1 , 0); a[mid * 2 + 3] = line(-1e10 , 1e10 , 0 , 1) , a[mid * 2 + 4] = line(-1e10 , 1e10 , 1 , 0); sort(a + 1 , a + mid * 2 + 5 , cmp); for(i = 2 ; i <= mid * 2 + 4 ; i ++ ) if(a[i].ang != a[i - 1].ang) a[++tot] = a[i]; q[1] = a[1]; for(i = 2 ; i <= tot ; i ++ ) { while(l < r && left(a[i] , p[r - 1])) r -- ; while(l < r && left(a[i] , p[l])) l ++ ; q[++r] = a[i]; if(l < r) p[r - 1] = inter(q[r - 1] , q[r]); } while(l < r && left(q[l] , p[r - 1])) r -- ; p[r] = inter(q[r] , q[l]); return r - l > 1; } int main() { int n , i , l = 1 , r , mid , ans = 0; scanf("%d" , &n) , r = n; for(i = 1 ; i <= n ; i ++ ) scanf("%Lf%Lf%Lf" , &px[i] , &py[i] , &pz[i]); while(l <= r) { mid = (l + r) >> 1; if(judge(mid)) ans = mid , l = mid + 1; else r = mid - 1; } printf("%d\n" , ans); return 0; }
【bzoj2732】[HNOI2012]射箭 二分+半平面交