bzoj 2732 [HNOI2012]射箭 二分答案+半平面交

题面

题目传送门

解法

  • 半平面交模板题。
  • 首先可以二分答案,然后考虑如何检验答案是否合法。
  • 现在对于每一个靶子都有一个形如 l ≤ a x 2 + b x ≤ r l\leq ax^2+bx\leq r lax2+bxr的限制, x , l , r x,l,r x,l,r为常量,那么就可以看成对 a , b a,b a,b的限制,转化成半平面交。
  • 其实不需要每一次二分的时候都进行一次排序,可以先排序,然后将编号在二分范围内的半平面取出即可。
  • 时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)

【注意事项】
说实话这道题调了我好久……
实现的时候一定要注意精度问题……

  • 为了满足实际生活的限制,必须满足抛物线的 a &lt; 0 , b &gt; 0 a&lt;0,b&gt;0 a<0,b>0,那么就相当于限制半平面交必须在第二象限内,加上4条关于第二象限的限制的半平面。
  • 可能会出现所有半平面交于同一点的情况,那么在删除队列中直线的过程中不能将点在直线上判断为删除该直线的条件之一。
  • 在实现半平面交的过程中,一定要先弹出队尾,再弹出队首,否则会出现一些问题。这个建议自己亲身试验一下……
  • e p s eps eps i n f inf inf一定要开大,一开始开到 1 0 − 12 10^{-12} 1012 1 0 12 10^{12} 1012一直过不去,然后开成 1 0 − 15 10^{-15} 1015 1 0 15 10^{15} 1015就过了……
  • 需要注意的细节应该就这么多吧……

代码

#include <bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template <typename T> void read(T &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
const int N = 200010; const ld eps = 1e-15l, inf = 1e15l;
struct pt {ld x, y;};
struct line {pt a, b; ld ang; int id;} l[N], q[N], tx[N];
struct Node {ll x, y1, y2;} a[N];
pt operator + (pt a, pt b) {return (pt) {a.x + b.x, a.y + b.y};}
pt operator - (pt a, pt b) {return (pt) {a.x - b.x, a.y - b.y};}
pt operator * (pt a, ld b) {return (pt) {a.x * b, a.y * b};}
ld operator * (pt a, pt b) {return a.x * b.y - a.y * b.x;}
bool operator < (line a, line b) {return a.ang != b.ang ? a.ang < b.ang : (a.b - b.a) * (b.b - b.a) > eps;}
pt inter(line a, line b) {
	pt x = a.a - b.a, y = a.b - a.a, z = b.b - b.a;
	ld t = (z * x) / (y * z); return a.a + y * t;
}
bool chkr(line a, line b, line c) {
	pt t = inter(a, b);
	return (c.b - c.a) * (t - c.a) < 0;
}
bool judge(int tn) {
	int n = 0;
	for (int i = 1; i <= tn; i++) {
		if (fabs(l[i].ang - l[i - 1].ang) > eps) n++;
		l[n] = l[i];
	}
	int tl = 1, tr = 2; q[1] = l[1], q[2] = l[2];
	for (int i = 3; i <= n; i++) {
		while (tl < tr && chkr(q[tr - 1], q[tr], l[i])) tr--;
		while (tl < tr && chkr(q[tl], q[tl + 1], l[i])) tl++;
		q[++tr] = l[i];
	}
	while (tl < tr && chkr(q[tr - 1], q[tr], q[tl])) tr--;
	while (tl < tr && chkr(q[tl], q[tl + 1], q[tr])) tl++;
	return tr - tl >= 2;
}
bool check(int mid, int n) {
	int tn = 0;
	for (int i = 1; i <= n; i++)
		if (tx[i].id <= mid) l[++tn] = tx[i];
	return judge(tn);
}
int main() {
	int n, tn = 0; read(n);
	for (int i = 1; i <= n; i++) read(a[i].x), read(a[i].y1), read(a[i].y2);
	for (int i = 1; i <= n; i++) {
		ld t1 = a[i].x * a[i].x, t2 = a[i].x, tl = a[i].y1, tr = a[i].y2;
		pt x = (pt) {0, tl / t2}, y = (pt) {1, (tl - t1) / t2}, t = y - x;
		tx[++tn] = (line) {x, y, atan2l(t.y, t.x), i};
		x = (pt) {1, (tr - t1) / t2}, y = (pt) {0, tr / t2}, t = y - x;
		tx[++tn] = (line) {x, y, atan2l(t.y, t.x), i};
	}
	pt x = (pt) {-inf, inf}, y = (pt) {-inf, eps}, t = y - x;
	tx[++tn] = (line) {x, y, atan2l(t.y, t.x), 0};
	x = (pt) {-inf, eps}, y = (pt) {-eps, eps}, t = y - x;
	tx[++tn] = (line) {x, y, atan2l(t.y, t.x), 0};
	x = (pt) {-eps, eps}, y = (pt) {-eps, inf}, y = y - x;
	tx[++tn] = (line) {x, y, atan2l(t.y, t.x), 0};
	x = (pt) {-eps, inf}, y = (pt) {-inf, inf}, t = y - x;
	tx[++tn] = (line) {x, y, atan2l(t.y, t.x), 0};
	sort(tx + 1, tx + tn + 1); int l = 2, r = n, ans = 1;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (check(mid, tn)) ans = mid, l = mid + 1;
			else r = mid - 1;
	}
	cout << ans << '\n';
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值