几何入门合集 gym101968 problem F. Mirror + gym102082 Problem F Fair Chocolate-Cutting + gym101915 problem B. Ali and Wi-Fi

abstract:

  1. V const & a 加速

F. Mirror

题意

链接

问题:
有n个人在y=0的平面上(及xoz平面)。z=0平面上有一面镜子(边平行于坐标轴)。z=a平面上有q个点(保证a大于所有人的z坐标)。 所有人面朝镜子,且在镜子和q个点之间(即每个人的z坐标保证0<z<a)。
问对于某个点,让所有人能够通过镜子看到那个点的镜子的最小面积。


题解

三维几何+镜像+凸包
首先考虑镜面,我们可以通过(初中科学的)镜面反射原理,关于z=0做出z=a的对称平面z=-a。问题就变成了n个人看z=-a上的某个点。(下图绿点是人,红点是询问点)

然后观察,镜子的高和宽是独立的。 于是我们分别求它们的最大值即可。
求高比较简单,我们朝-x方向看yoz平面。通过把每个人跟点Q的像Q‘相连,我们发现离Q’z轴距离最近的人对应着镜子的下边界,最远的人对应着上边界,通过维护所有人z坐标的max_z&min_z以及相似三角形可以直接求出两个边界,复杂度为O(1)。
我们用同样的方法,朝-y方向看xoz平面。 通过把每个人跟点Q的像Q‘相连,我们发现,左右边界并不对应着最左边与最右边的人。而且随着询问点的变化,对应着左右边界的人也在变化。
如果我们暴力的找对应左右边界的人,复杂度为n*q ,不可行。
我们发现,对应着左右边界的人虽然随着询问点变化,但他们都在凸包上(如下图)。

更进一步,如果我们将询问点按照x坐标排序,随着询问的x坐标增加,左右边界的人在凸包上的变化是顺时针旋转的。(考虑你从左到右观察一个正前方的凸包)
于是我们就能够通过一个nlogn的凸包预处理然后O(1)地回答每个询问,复杂度为O(q+nlogn)

剩下的是实现”从左到右看凸包时凸包左右边界的顺时针更新“。
首先是写out,in函数(右手法则,向外转就是逆时针),用来逆时针、顺时针遍历凸包上的点。因为极角排序凸包存的点是逆时针的(极角排序的那个角是与y轴的逆时针夹角。),所以out就是++。
先找到凸包的下上顶点,作为初始的左右边界的对应点。
然后根据x坐标从小到大枚举询问点Q。
对于每个Q,不断顺时针更新左边界对应的人,直到他与Q的连线在他凸包上顺时针的下一个人与Q的连线的外面(直观上显然正确)。 右边界同理。
某些编辑器比如codeforces不能混用iostream与stdio


代码

#include<cmath>
#include<algorithm>
#include<iostream>
#include<stdio.h>
#include<map>
#include<string.h>
#include<queue>
#include<stack>
using namespace std;
#define debug(x) cerr<<#x<<" = "<<(x)<<endl
#define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
#define FAST_IO ios_base::sync_with_stdio(false); cin.tie(nullptr)
//#define double long long
typedef long long ll;
typedef double db;
const int maxn = 2e5 + 5;
const db eps = 1e-7;
int n, q, a;

long double ans[maxn];
bool eq(double a, double b) { return abs(a - b) <= eps; }
struct V {
	double x, y;
	V(double a = 0.0, double b = 0.0) :x(a), y(b) {}
	void sc() { scanf("%lf%lf", &x, &y); }
	double operator |(V const &o)const {
		return x * o.y - o.x * y;
	}
	bool operator <(V const &o)const {
		if (eq(x, o.x))return y < o.y;
		return x < o.x;
	}
	V operator -(V const &o)const { return V(x - o.x, y - o.y); }
	void pr() { printf("%lf %lf\n", x, y); }
}st[maxn];
pair<V, int> Q[maxn];
bool cmpr(V const &a, V const &b) {
	V v1 = a - st[0], v2 = b - st[0];
	return (v1 | v2) < -eps;
}

vector<V> ch;
void getCH() {
	sort(st + 1, st + n, cmpr);
	ch.push_back(st[0]);
	rep(i, 1, n-1) {
		while (ch.size() > 1 && (st[i] - ch.back() | ch.back() - ch[ch.size() - 2]) < eps)ch.pop_back();
		ch.push_back(st[i]);
	}
}
int out(int x) { return x ? x - 1 : ch.size() - 1; }
int in(int x) { return x + 1 == (int)ch.size() ? 0 : x + 1; }


double getx(double xs, double z, double xq) {
	return xs + z / (z + a) * (xq - xs);
}



int main() {
	//FAST_IO;
	int t;
	cin >> t;
	while (t--) {

		cin >> n >> a;
		
		rep(i, 0, n - 1)st[i].sc();
		db zmn = st[0].y, zmx = st[0].y;
		rep(i, 0, n - 1) {
			zmn = min(zmn, st[i].y);
			zmx = max(zmx, st[i].y);
		
		}
		rep(i, 0, n - 1)st[i].y = a - st[i].y;
		sort(st, st + n);
		ch.clear();
		getCH();

		cin >> q;
		ll qx = 0, qy = 0;
		rep(i, 1, q) {
			Q[i].first.sc();
			Q[i].second = i;
		}
		sort(Q + 1, Q + 1 + q);

		int lp = 0, rp = 0;
		rep(i, 0, ch.size() - 1)ch[i].y = a - ch[i].y;
		while (ch[in(rp)].y < ch[rp].y)rp = in(rp);
		while (ch[out(lp)].y > ch[lp].y)lp = out(lp);

		rep(i, 1, q) {
			while (true) {
				int ni = in(lp);
				if (getx(ch[ni].x, ch[ni].y, Q[i].first.x) < getx(ch[lp].x, ch[lp].y, Q[i].first.x))lp = ni;
				else break;
			}
			while (true) {
				int ni = in(rp);
				if (getx(ch[ni].x, ch[ni].y, Q[i].first.x) > getx(ch[rp].x, ch[rp].y, Q[i].first.x))rp = ni;
				else break;
			}
			double x = abs(getx(ch[rp].x, ch[rp].y, Q[i].first.x) - getx(ch[lp].x, ch[lp].y, Q[i].first.x));
			double h = getx(0, zmx, Q[i].first.y) - getx(0, zmn, Q[i].first.y);
			ans[Q[i].second] = (long double)x * h;
			//printf("%.20lf\n", x*h);
		}
		rep(i, 1, q)printf("%.20lf\n", (double)ans[i]);
	}
	cin >> n;
}

/*
1
3 3
-2 1
7 2
3 1
3
2 5
-2 4
8 10
*/



心路历程

当有两个以上的bug时你就炸了
wa0:FAST_IO codeforces以用就wa

wa1:输出rep(i,1,q) not rep(i,1,n)//最后才发现
wa2:凸包板子里面下标从0开始。


Problem F Fair Chocolate-Cutting

题意

来源:ICPC Asia Regional Contest, Yokohama, 2018–12–09

让你求所有平分凸多边形的线段中最长和最短的那两条。
输出它们的长度。

题解

证明题+硬核平面几何
我们有结论:(目前不会证明)
某合法(平分多边形的)线段的端点在顶点上时一定是局部最长或最短的。
除此之外,当某合法线段垂直于任意“外角”平分线时,也是局部最短的

于是问题就转化成
1.对于每个顶点,找到过它的合法线段。

2.对于每个角(跳过一些线段的“相邻”两条线段的延长线交出的角),找到垂直它的平分线的合法线段。注意特判掉角不存在的情况,即两边平行。

对于1,只要从该顶点开始不断逆时针扫顶点,直到刚刚超过1/2的总面积。然后把上一个三角形分割一下。
对于2,直接用平面几何知识以及三角函数求解

代码

#include<complex>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include<algorithm>
#include<iomanip>
typedef long long ll;
typedef double db;
typedef long double ld;

using namespace std;
typedef complex<double>  point;
#define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
#define F(i,N)for(int i = 0;i < (int)(N);i ++)
#define Decimal fixed<<setprecision(20)
#define PB push_back
#define EB emplace_back
#define X real();
#define Y imag();
#define curr(PP,i) PP[i]
#define next(PP,i) PP[(i+1)%PP.size()]
#define diff(PP,i) (next(PP,i)-curr(PP,i))
const double eps = 1e-7;
const double PI = acos(-1.0);
const double INF = 1e18;
int n;
vector<point> V;
double sumS,nowS,mx,mn;
double cross(const point& a, const point& b) {
	return imag(conj(a)*b);
}
double dot(const point& a, const point& b) {
	return real(conj(a)*b);
}
double area(const vector<point>& p) {
	double A = 0;
	F(i, p.size()) A += cross(curr(p, i), next(p, i));
	return A / 2.;
}
double triArea(int a, int b, int c) {
	a %= n, b %= n, c %= n;
	if (a == b || b == c || c == a)return 0;
	vector<point> tmp = { V[a],V[b],V[c] };
	return area(tmp);
}
double distancePP(point const &a, point const &b) {
	return abs(a - b);
}
//seg
struct segment :public vector<point> {
	segment(const point &a, const point &b) {
		push_back(a); push_back(b);
	}
};
point crosspoint(segment const &l,segment const &m) {
	double A = cross(l[1] - l[0], m[1] - m[0]);
	double B = cross(l[1] - l[0], l[1] - m[0]);
	if (abs(A) < eps&&abs(B) < eps)return m[0];//same line
	if (abs(A) < eps)return point(INF, INF);//parallel
	return m[0] + B / A*(m[1] - m[0]);
}
double angle(const point& a, const point& b) {
	auto tmp = abs(arg(a) - arg(b));
	return min(tmp, 2 * PI - tmp);
}
double angle(const segment &s1, const segment &s2) {
	return angle(s1[1] - s1[0], s2[1] - s2[0]);
}
point projection(const segment &l, const point &p) {
	double t = dot(p - l[0], l[0] - l[1]) / norm(l[0] - l[1]);
	return l[0] + t * (l[0] - l[1]);
}
double distanceLP(const segment &l, const point &p) {
	return abs(p - projection(l, p));
}
bool intersectSP(const segment &s,const point &p) {//point on seg
	return abs(s[0] - p) + abs(s[1] - p) - abs(s[1] - s[0]) < eps;
}
double seica(int a,int b) {
	a %= n; b %= n;
	vector<point> tmp;
	int i = a;
	while (1) {
		tmp.EB(V[i]);
		if (i == b)break;
		i = (i + 1) % n;
	}
	if (tmp.size() < 3)return 0;
	else return area(tmp);
}
void checkV(int p,int q) {
	p %= n; q %= n;
	double remain = (sumS / 2) - nowS;
	double rate = remain / triArea(p, q, q + 1);
	point tmp = V[q] + rate * (V[(q + 1) % n] - V[q]);
	mn = min(mn, distancePP(V[p], tmp));
	mx = max(mx, distancePP(V[p], tmp));
}
void checkSeg(int p, int q) {
	p %= n, q %= n;
	nowS -= triArea(p, p + 1, q);
	segment a(V[(p + 1) % n], V[p]);
	segment b(V[(q) % n], V[(q + 1) % n]);
	point crossp = crosspoint(a, b); 
	if (crossp == point(INF, INF)) {
		double sqS = area({ V[p],V[(p + 1) % n],V[q],V[(q + 1) % n] });
		if (nowS + sqS < sumS / 2 + eps)return;
		bool flag = 0;
		if (intersectSP(b, projection(b, a[0])) || intersectSP(b, projection(b, a[1])))flag = 1;
		if (intersectSP(a, projection(a, b[0])) || intersectSP(a, projection(a, b[1])))flag = 1;
		if (!flag)return;
		mn = min(mn, distanceLP(b, a[0]));
		return;
	}
	double tmpS=0;
	if (distancePP(crossp, a[0]) > distancePP(crossp, a[1]) + eps) {
		tmpS = seica(q + 1, p);
		swap(a[0], a[1]);
		swap(b[0], b[1]);
	}
	else {
		tmpS= seica(p + 1, q);
	}
	double theta = angle(a, b);
	double T = distancePP(crossp, a[0])*distancePP(crossp, b[0])*sin(theta) / 2;
	double leg = (2 * (T - tmpS) + sumS) / sin(theta);
	bool flag = 0;
	if (leg > distancePP(a[1], crossp)*distancePP(a[1], crossp) + eps)flag = 1;
	if (leg + eps < distancePP(a[0], crossp)*distancePP(a[0], crossp))flag = 1;
	if (leg > distancePP(b[1], crossp)*distancePP(b[1], crossp) + eps)flag = 1;
	if (leg + eps < distancePP(b[0], crossp)*distancePP(b[0], crossp))flag = 1;
	if (flag)return;
	mn = min(mn, 2*sqrt(leg)*sin(theta / 2));
}
int main() {
	mx = 0;
	mn = 1e18;
	cin >> n;
	F(i, n) {
		db a, b;
		cin >> a >> b;
		V.EB(a, b);
	}
	int q = 1;
	sumS = area(V);
	F(i, n) {
		while (nowS + triArea(i, q, q + 1) < sumS / 2 + eps) {
			nowS += triArea(i, q, q + 1);
			q++;
		}
		checkV(i,q);
		checkSeg(i,q);
	}
	cout << Decimal << mn << endl;
	cout << Decimal << mx << endl;
	cin >> n;
	return 0;
}






/*
4
0 0
10 0
10 10
0 10


3
0 0
6 0
3 10
*/



心路历程


关键是不会证明。  
复数大法好啊!!!(其实还是慢5倍??下面那题)



B. Ali and Wi-Fi

题意

来源ACM International Collegiate Programming Contest, Tishreen Collegiate Programming Contest
100个圆,每个圆代表wifi覆盖区域,每个wifi有一个网速。你最多可以连入m个WiFi,网速为这些wifi的总和。
问你最大的网速是多少。


题解

想法题+圆模板
我们知道n个圆最多可以把平面分成n^2-n+2个区域(递推进阶题233)。所以可以考虑遍历每个区域,计算它被哪些圆。。。
我们发现维护一块区域很难,而要取最大的m个更难考虑。
于是考虑维护圆与圆的交点。我们发现每个区域都是圆交出来的,i.e.交点都在区域的边界上。
于是我们可以枚举交点来代替枚举区域,显然交点个数也是n^2级别的。(考虑圆内含的情况,所以要把每个圆心也枚举一遍)
这样枚举的另一个好处是可以很简单地处理m:
枚举每个交点,计算有几个圆包含它。然后直接在这些圆中取最大的m个。
复杂度为n^2nnlogn。
如果我们用优先队列来维护最大的m个,那么复杂度里可以去掉一个n。


代码

#include<cmath>
#include<algorithm>
#include<iostream>
#include<stdio.h>
#include<map>
#include<string.h>
#include<queue>
#include<stack>
#include<iomanip>
#include<complex>
using namespace std;
#define debug(x) cerr<<#x<<" = "<<(x)<<endl
#define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
#define F(i,N)for(int i = 0;i < (int)(N);i ++)
#define FAST_IO ios_base::sync_with_stdio(false); cin.tie(nullptr)
#define EB emplace_back
#define Decimal fixed<<setprecision(20)
#define fi first
#define se second
#define INF 1000000000


typedef long long ll;
typedef double db;
typedef long double ld;
#define double long double
//typedef complex<double> point;
#define X real()
#define Y imag()
const int maxn = 2e5 + 5;
const db eps = 1e-10;
const db PI = acos(-1.);
int dcmp(ld x)
{
	if (fabs(x) < eps) return 0;
	return x < 0 ? -1 : 1;
}
struct V {
	ld x, y;
	V() {}
	void sc() { scanf("%lf%lf", &x, &y); }
	V(double a, double b) : x(a), y(b) { }
	V operator+(const V &o)const { return V(x + o.x, y + o.y); }
	V operator-(const V &o)const { return V(x - o.x, y - o.y); }
	double L() { return sqrt(x * x + y * y); }
	V N() {
		double l = L();
		return V(x / l, y / l);
	}
	V rot(double th) { return V(x * cos(th) - y * sin(th), x * sin(th) + y * cos(th)); }
	V operator*(double z) { return V(x * z, y * z); }
	double operator*(const V &o) const { return x * o.x + y * o.y; }
	double operator|(const V &o) const { return x * o.y - o.x * y; }
	double operator==(const V &o) const { return dcmp(x - o.x) == 0 && dcmp(y - o.y) == 0; }
	void pr() { printf("%lf %lf\n", x, y); }
};
vector<V> p;
typedef V point;
V conj(V p) { return V(p.x, -p.y); }
double arg(V p) { return atan2(p.y, p.x); }
double norm(V p) { return p.L()*p.L(); }
double abs(V p) { return p.L(); }



double cross(const point& a, const point& b) {
	return imag(conj(a)*b);
}
double dot(const point& a, const point& b) {
	return real(conj(a)*b);
}
struct circle
{
	point c;
	ld r, v;
	circle() {}
	circle(point c, ld r, ld v) :c(c), r(r), v(v) {}

	inline point pt(double a)
	{
		return point(c.x + cos(a)*r, c.y + sin(a)*r);
	}
};
int getCircleCircleIntersection(circle C1, circle C2, point &t1, point &t2)
{
	ld d = abs(C1.c - C2.c);
	if (dcmp(d) == 0)
	{
		if (dcmp(C1.r - C2.r) == 0) return -1;// same cir
		return 0;// include
	}
	if (dcmp(C1.r + C2.r - d) < 0) return 0;// disjoint
	if (dcmp(fabs(C1.r - C2.r) - d) > 0) return 0;// include
	ld a = arg(C2.c - C1.c);
	ld da = acos((C1.r*C1.r + d * d - C2.r*C2.r) / (2 * C1.r*d));
	point p1 = C1.pt(a - da), p2 = C1.pt(a + da);
	t1 = p1;
	if (p1 == p2) return 1;
	t2 = p2;
	return 2;
}
vector<circle> A;
circle aa[105];
vector<point> P;
point pp[10105];
priority_queue<int > Q;
int  ans, sum;
int n, m, tot;
int main() {
	FAST_IO;
	int t;

	cin >> t;
	while (t--) {

		tot = ans = 0;
		cin >> n >> m;
		F(i, n) {

			cin >> aa[i].c.x >> aa[i].c.y >> aa[i].r >> aa[i].v;

		}
		F(i, n) {
			rep(j, i + 1, n - 1) {
				point t1, t2;
				int num = getCircleCircleIntersection(aa[i], aa[j], t1, t2);
				if (num == 1) {
					pp[tot++] = t1;
				}
				else if (num == 2) {
					pp[tot++] = t1;
					pp[tot++] = t2;
				}
			}
		}
		F(i, n)pp[tot++] = aa[i].c;
		F(j, tot) {
			F(i, n) {
				if (dcmp(abs(pp[j] - aa[i].c) - aa[i].r) <= 0) {
					Q.push(-aa[i].v);
					if (Q.size() > m)Q.pop();
				}
			}
			sum = 0;
			while (!Q.empty()) {
				sum -= Q.top();
				Q.pop();
			}
			ans = max(ans, sum);
		}
		cout << ans << endl;
	}
	cin >> t;
	return 0;
}



心路历程

没初始化。
瞎debug发现long double int求和会爆精度。
priority_Q太久没用,忘记pop是从头上弹出的了orz。
vector 和对于1e6的数据其实只比数组慢10ms。


posted @ 2019-04-08 12:41  SuuTTT  阅读(493)  评论(0编辑  收藏  举报