【清华2019冬令营模拟12.8】视野

计算几何弱渣果然就是一点感觉也没有。

题目大意;

在这里插入图片描述

题解:

首先考虑不删怎么做?

肯定要把点给离散,那么现在对于每一小段,要求出是哪条线段最近?

按一个顺序扫过去,每一条线段打一个加入和删除的标记。

由于线段互不相交,所以线段顺序不会随着小段的移动而改变。

因此,我们可以用一个set去维护插入删除,比较远近时,就求交,判断谁近。

那么第一问答案就出来了。

第二问、第三问都是一样的。

删掉一条线段,对一小段来说,如果删掉了最近的那一条线段,答案会增加它和第二条线段的面积,那么维护v[i]表示删掉第i条线段面积增加多少,ans1=ans0+max(v[i])

第三问,首先答案是ans0+max(v[i])+cmax(v[i])

但是我们会发现我们少考虑了一种情况,那就是如果线段i和线段j是一小段的最近和次近,其实选择他们还会增加第二条线段到第三条线段的面积,随便做。

总复杂度是 O ( n l o g n ) O(n log n) O(nlogn)

注意用atan2(y,x)这个函数可以方便地做几角排序。
Code:

#include<set>
#include<cmath>
#include<cstdio>
#include<algorithm>
#define db double
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;

const int N = 5e4 + 5;

int n;

const db eps = 1e-8;

struct P {
    db x, y;
    P(){}
    P(db x_, db y_){x = x_, y = y_;}
} p, q;
struct L {
    P p, v;
    L(){}
    L(P p_, P v_){p = p_, v = v_;}
};
P operator +(P a, P b) {return P(a.x + b.x, a.y + b.y);}  
P operator -(P a, P b) {return P(a.x - b.x, a.y - b.y);}  
P operator *(P a, db b) {return P(a.x * b, a.y * b);}  
db operator ^(P a, P b) {return a.x * b.y - a.y * b.x;}  
db dot(P a, P b) {return a.x * b.x + a.y * b.y;}
db len(P a) {return sqrt(dot(a, a));}  
P jd(L a, L b) {return b.p + b.v * ((a.v ^ (a.p - b.p)) / (a.v ^ b.v));}

L a[N], ty, tz;
struct nod {
	int x;
	nod(int _x = 0) {x = _x;}
};
bool operator <(nod x, nod y) {
	db u = len(jd(a[x.x], ty)), v = len(jd(a[y.x], ty));
	if(abs(u - v) < eps) return x.x < y.x;
	return u < v;
}
set<nod> s;

struct nod2 {
	P p;
	int x; 
} d[N * 2]; int d0, D;

const db pi = acos(-1);
db jj(P p) {
	return atan2(p.y, p.x);
}
int cmp2(nod2 a, nod2 b) {
	return jj(a.p) < jj(b.p);
}

int c[N][2], c0[N];

struct edge {
	int fi[N * 2], nt[N * 4], to[N * 4], v[N * 4], tot;
	void link(int x, int y, int z) {
		nt[++ tot] = fi[x], to[tot] = y, v[tot] = z, fi[x] = tot;
	}
} e;

db ans0, ans1, ans2, v[N];

db calc(int x) {
	return abs(jd(ty, a[x]) ^ jd(tz, a[x])) / 2;
}

struct nod3 {
	int x, y; db z;
} w[N * 2]; int w0;

int cmp3(nod3 a, nod3 b) {
	if(a.x < b.x) return 1;
	if(a.x > b.x) return 0;
	return a.y < b.y;
}

int main() {
	freopen("area.in", "r", stdin);
	freopen("area.out", "w", stdout);
	scanf("%d", &n);
	fo(i, 1, n) {
		scanf("%lf %lf %lf %lf", &p.x, &p.y, &q.x, &q.y);
		d[++ d0].p = p; d[d0].x = i;
		d[++ d0].p = q; d[d0].x = i;
		a[i].p = p; a[i].v = q - p;
	}
	{
		sort(d + 1, d + d0 + 1, cmp2);
		fo(i, 1, d0) {
			if(i == 1 || abs(d[i].p ^ d[D].p) > eps) d[++ D] = d[i];
			int x = d[i].x;
			if(!c0[x]) c[x][c0[x] ++] = D; else {
				c[x][1] = D;
				if((d[c[x][0]].p ^ d[c[x][1]].p) < 0)
					swap(c[x][0], c[x][1]);
			}
		}
	}
	fo(i, 1, n) {
		if(c[i][0] < c[i][1])
			e.link(c[i][1], i, -1), e.link(c[i][0], i, 1); else
			e.link(c[i][1], i, -1), e.link(1, i, 1), e.link(c[i][0], i, 1);
	}
	fo(i, 1, D) {
		ty.v = d[i].p; tz.v = d[i % D + 1].p;
		for(int j = e.fi[i]; j; j = e.nt[j]) {
			int x = e.to[j];
			if(e.v[j] == 1) s.insert(nod(x)); else s.erase(s.find(nod(x)));
		}
		if(s.empty()) {
			ans0 = ans1 = ans2 = 1e15;
		} else {
			int x = (*s.begin()).x;
			ans0 += calc(x);
			if(s.size() == 1) {
				ans1 = ans2 = 1e15;
			} else {
				int y = (*++s.begin()).x;
				v[x] += calc(y)	- calc(x);
				if(s.size() == 2) {
					ans2 = 1e15;
				} else {
					int z = (*++(++s.begin())).x;
					w[++ w0].x = x; w[w0].y = y;
					w[w0].z = calc(z) - calc(y);
				}
			}
		}
	}
	fo(i, 1, n)	ans1 = max(ans1, ans0 + v[i]);
	db c0 = 0, c1 = 0;
	fo(i, 1, n) if(v[i] > c1)
		c0 = c1, c1 = v[i]; else
	if(v[i] > c0) c0 = v[i];
	if(ans2 < 1e10) ans2 = ans0 + c0 + c1;
	sort(w + 1, w + w0 + 1, cmp3);
	fo(i, 2, w0 + 1) if(i > w0 || w[i - 1].x != w[i].x || w[i - 1].y != w[i].y) {
		ans2 = max(ans2, ans0 + v[w[i - 1].x] + v[w[i - 1].y] + w[i - 1].z);
	} else w[i].z += w[i - 1].z;
	if(ans0 > 1e10) printf("infinite\n"); else printf("%.2lf\n", ans0);
	if(ans1 > 1e10) printf("infinite\n"); else printf("%.2lf\n", ans1);
	if(ans2 > 1e10) printf("infinite\n"); else printf("%.2lf\n", ans2);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值