[CEOI2020 D1T2 / CF1402B] 道路Roads 题解

我的CEOI作战记录&题解-洛谷博客

我的CEOI作战记录&题解-cnblogs

题目链接-luogu 题目链接-Codeforces


计算几何扫描线题.

首先为了防止出现一些边界情况,(比如有一条无斜率的线段),可以把点转一个角度。

用set维护当前的所有线段,并记录每条线段上方,最晚被插入/删除的后继的线段。

然后从左到右扫描线,

在每次加入线段的时候,考虑把加入的线段的左端点和set里的一个点连接起来。

考虑这个线段在set上的前驱,如果前驱上记录了点,那我就把这个点和记录的点连起来,否则我就和前驱这条线段的左端点连起来。 同时用当前这条线段的左端点来更新前驱记录的点。

在每次删除点的时候,更新一下set上的前驱记录的点即可。

复杂度(O(n log n).)

代码 :

#define db long double
const int N = 100050;
db Pi = acos(-1.0),theta = Pi * (1.0 * 139 / 180),cost = cos(theta),sint = sin(theta);
struct point{
	db x,y; int realx,realy;
	inline void calc(){ x = cost * realx - sint * realy,y = cost * realy + sint * realx; }
}p[N<<1]; int n;
inline void print(point a,point b){
	cout << a.realx << ' ' << a.realy << ' ' << b.realx << ' ' << b.realy << '
';
}
struct Event{
	int id,tp; db x;
	bool operator < (Event w) const{ return x < w.x; }
}ev[N<<1];
db T;
struct line{
	int s,t; mutable int lst; db k,b;
	inline void calc(){ k = (p[s].y - p[t].y) / (p[s].x - p[t].x),b = p[s].y - k * p[s].x; }
	bool operator < (const line w) const{ return k*T+b < w.k*T+w.b; }
}tmp;
set<line>S; set<line>::iterator it;
int main(){
	int i;
	ios::sync_with_stdio(0);
	cin >> n;
	for (i = 1; i <= n*2; i += 2){
		cin >> p[i].realx >> p[i].realy,p[i].calc();
		cin >> p[i+1].realx >> p[i+1].realy,p[i+1].calc();
		if (p[i].x > p[i+1].x) swap(p[i],p[i+1]);
		ev[i].id = i,ev[i].tp = 1,ev[i].x = p[i].x;
		ev[i+1].id = i+1,ev[i+1].tp = 0,ev[i+1].x = p[i+1].x;
	}
	sort(ev+1,ev+(n<<1|1));
	tmp.s = -2,tmp.t = tmp.lst = 0,tmp.k = 0,tmp.b = -1e10; S.insert(tmp);
	tmp.s = -1,tmp.t = tmp.lst = 0,tmp.k = 0,tmp.b = 1e10; S.insert(tmp);
	for (i = 1; i <= n*2; ++i){
		T = ev[i].x;
		if (ev[i].tp){
			tmp.lst = tmp.s = ev[i].id,tmp.t = ev[i].id+1,tmp.calc();
			it = S.insert(tmp).first,--it;
			if (it->lst) print(p[it->lst],p[tmp.s]); it->lst = tmp.s;
		}
		else{
			tmp.s = ev[i].id-1,tmp.t = ev[i].id,tmp.calc(),it = S.find(tmp),--it;
			it->lst = tmp.t,S.erase(tmp);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/s-r-f/p/13591167.html