UVA1398 Meteor 扫描线

UVA1398 Meteor 扫描线

题目链接

​ 还是搬一下lrj蓝皮书的翻译吧:

​ 给定一个矩形, 左下角为(0, 0),右上角为\((w,h)\), 有\(n\)个流星, 每个流星有初始位置\((x,y)\)和横向的移动速度\(a\), 纵向的移动速度\(b\), (若为整数是向上, 右, 若为负数是向下, 左). 假设\(p\)是初始位置, \(v\)为速度, 在时刻\(t(t >= 0)\)的位置就是\(p + vt\).

​ 求出在某一个时刻矩形内最多的流星的数量, 在矩形边缘的流星不算.\(n <= 1e5\).

​ 扫描线.

​ 这个题第一眼看上去并没有什么思路.阅读lrj的书后, 我们可以把问题转化成这样:

​ 每个流星\(i\)经过矩形会有一个时间段\((L_i, R_i)\), 注意是开区间. 这样数轴上将会出现\(i\)条线段, 我们要找一个时刻\(t\),也就是数轴上找一个点, 使最多的线段包含它.

​ 找这个点肯定不能枚举, 那么我们需要用到扫描线算法.

​ 我们把"碰到左端点", "碰到右端点"都看作是一个事件, 然后按时间的出现顺序排序, 如果两个事件的出现时间一样, 那么把 "碰到右端点"的事件排在前面, 一定要这么排, 不然会错.

​ 然后遍历每个事件, 对于事件一就让答案加一, 对于事件二就让答案减一.

#include <bits/stdc++.h>

using namespace std;

inline long long read() {
	long long s = 0, f = 1; char ch;
	while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
	return s * f;
}

const int N = 2e5 + 5;
int n, w, h, cnt, res, ans;
double L, R;
struct LINE { 
	double p; int f; 
	LINE() {} LINE(double x, int z) { p = x; f = z;}
} line[N]; 

void make_line(int x, int a, int w, double &L, double &R) {
	if(a == 0) { if(x <= 0 || x >= w) R = L - 1; return ; }
	else if(a > 0) {
		L = max(L, -1.0 * x / a); R = min(R, 1.0 * (w - x) / a); return ;
	}
	else {
		L = max(L, 1.0 * (w - x) / a); R = min(R, -1.0 * x / a); return ; 
	}
}

int cmp(LINE a, LINE b) { return a.p == b.p ? a.f < b.f : a.p < b.p; } 

int main() {

	for(int T = read(); T ; T --) {
		w = read(); h = read(); n = read(); cnt = ans = res = 0;
		for(int i = 1, x, y, a, b;i <= n; i++) {
			x = read(), y = read(), a = read(), b = read();
			L = 0; R = 1e9;
			make_line(x, a, w, L, R); make_line(y, b, h, L, R);
			if(R > L) line[++ cnt] = LINE(L, 1), line[++ cnt] = LINE(R, -1);
		}
		sort(line + 1, line + cnt + 1, cmp);
		for(int i = 1;i <= cnt; i++) {
			if(line[i].f == 1) res ++, ans = max(ans, res);
			else res --;
		}
		printf("%d\n", ans);
	}
	
	return 0;
}
posted @ 2020-11-26 22:32  C锥  阅读(56)  评论(0编辑  收藏  举报