计算几何学 |
几何公式 |
poj1265(pick定理) |
叉积和点积的运用 |
poj2031,poj1039 |
|
多边型的简单算法和相关判定 |
poj1408,poj1584 |
|
凸包 |
poj2187,poj1113 |
POJ 1265
这题貌似。。。pick定理+线段上的整数点的个数+叉积求多边形面积。。。
pick定理:http://www.cnblogs.com/vongang/archive/2012/04/07/2435741.html
线段上的整数点的个数:算导上的推论,方程ax ≡ c (mod b)或者对模n有d个不同的解,或则无解。 同余方程可写成 ax + by = c. 即是线段ab上有d个整数点。
叉积求多边形面积:明白叉积的概念就很清楚了,设叉积 A = p0p1 × p0p2 。|A|表示平行平行四边形p0p1p0'p2的面积。如果A < 0 表示p1 在p2 的逆时针方向上。以某一个点为p0,则所求的多边形的面积就是沿顺时针方向两两相邻的所有叉积和。(网上有很多证明。)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int MAXN = 110;
struct pot {
int x;
int y;
pot(int a = 0, int b = 0) : x(a), y(b) {}
pot operator + (const pot b) {
return pot(x + b.x, y + b.y);
}
}p[MAXN];
int det(pot a, pot b) {
return a.x * b.y - a.y * b.x;
}
int gcd(int a, int b) {
if(b == 0) return a;
return gcd(b, a % b);
}
int main() {
//freopen("data.in", "r", stdin);
int T, n, i, B, I, cas = 0;
double area;
pot u;
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
p[0] = pot(0, 0);
B = I = 0;
for(i = 1; i <= n; ++i) {
scanf("%d%d", &u.x, &u.y);
B += abs(double(gcd(u.x, u.y)));
p[i] = p[i-1] + u;
}
area = 0;
for(i = 0; i <= n; ++i) {
//printf("%d %d\n", p[i].x, p[i].y);
area += det(p[i], p[i+1]);
}
area = abs(area)/2;
I = area + 1 - B/2;
printf("Scenario #%d:\n%d %d %.1lf\n", ++cas, I, B, area);
if(T) printf("\n");
}
return 0;
}
POJ 2031
这题看题目挺吓人的,还三维坐标系。其实就是求出各球之间的距离,然后prim求最小生成树。幸亏有这一句 you may consider that two corridors never intersect,要不然有得恶心了。。。
渣代码:
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> using namespace std; const int MAXN = 110; const double eps = 1e-8; const double inf = 30010.0; struct node { double x; double y; double z; double r; } cir[MAXN]; double mp[MAXN][MAXN]; double low[MAXN]; bool vis[MAXN]; int n; double cmp(double x) { if(x > eps) return 1; else if(x < -eps) return -1; return 0; } double dis(node a, node b) { a.x -= b.x; a.y -= b.y; a.z -= b.z; double d = sqrt(a.x*a.x + a.y*a.y + a.z*a.z) - a.r - b.r; if(cmp(d) <= 0) return 0; return d; } double prim() { int i, j, f; double sum = 0, min; for(i = 0; i < n; ++i) { low[i] = mp[0][i]; vis[i] = false; } low[0] = 0; vis[0] = true; for(i = 1; i < n; ++i) { min = inf; f = 0; for(j = 1; j < n; ++j) { if(!vis[j] && min > low[j]) { min = low[j]; f = j; } } if(f != 0) sum += min; vis[f] = true; for(j = 1; j < n; ++j) { if(!vis[j] && low[j] - mp[f][j] > eps) { low[j] = mp[f][j]; } } } return sum; } int main() { //freopen("data.in", "r", stdin); int i, j; double d, ans; while(scanf("%d", &n), n) { for(i = 0; i < n; ++i) { for(j = 0; j < n; ++j) { if(i == j) mp[i][j] = 0; else mp[i][j] = inf; } } for(i = 0; i < n; ++i) { scanf("%lf%lf%lf%lf", &cir[i].x, &cir[i].y, &cir[i].z, &cir[i].r); for(j = 0; j < i; ++j) { d = dis(cir[j], cir[i]); mp[i][j] = mp[j][i] = d; } } ans = prim(); printf("%.3lf\n", ans); } return 0; }
POJ 1039
黑书上的练习题,为了这道题我啥都没干,看了一天多的黑书。。。=_=! 以下内容摘自黑书:
题意:有一宽度为1的折线管道,上面顶点为(xi,yi),所对应的下面顶点为(xi,yi-1),假设管道都是不透明的,不反射的,光线从左边入口处的(x0,y0),(x,y0-1)之间射入,向四面八方传播,求解光线最远能传播到哪里(取x坐标)或者是否能穿透整个管道.
如果一根光线自始至终都未擦到任何顶点,那么它肯定不是最优的,因为可以通过平移来使之优化,如果只碰到一个顶点,那也不是最优的,可以通过旋转,使它碰到另一个顶点,并且更优,即最优光线一定擦到一个上顶点和一个下顶点.
所以可以任取一个上顶点和一个下顶点,形成直线L,若L能射入左入口,即当x=x0时,直线L在(x0,y0)和(x0,y0-1)之间,则是一条可行光线.再从左到右一次判断每条上,下管道是否与L相交,相交则求交点,并把交点x值与当前最佳值比较,若所有管壁都不予L相交,则说明L射穿了整个管道.
然后参考庄神的模板做的,Orz。。。
渣代码:
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> using namespace std; const int MAXN = 100; const double eps = 1e-8; const double inf = ~0u; struct point { double x; double y; }; point up[MAXN], dn[MAXN]; int n; int dbcmp(double x) { if(x > eps) return 1; else if(x < -eps) return -1; return 0; } double det(double x1, double y1, double x2, double y2) { return x1*y2 - x2*y1; } double cross(point a, point b, point c) { return det(b.x - a.x, b.y - a.y, c.x - a.x, c.y - a.y); } int segcross(point a, point b, point c, point d) { //判线段与直线相交,做一次跨立实验即可 int d1, d2; d1 = dbcmp(cross(a, b, c)); d2 = dbcmp(cross(a, b, d)); if(d1*d2 <= 0) return true; return false; } double get_x(point a, point b, point c, point d) { //从题意可以知道,不存在与y轴平行的直线,所以可以直接用点斜式求交点 double k1, k2, c1, c2; k1 = (a.y - b.y)/(a.x - b.x); k2 = (c.y - d.y)/(c.x - d.x); c1 = a.y - k1*a.x; c2 = c.y - k2*c.x; return (c2 - c1)/(k1 - k2); } double solve(point a, point b) { int i = 0; double t, ans = -inf; while(i < n && segcross(a, b, up[i], dn[i])) ++i; if(i == 0) return -inf; if(i == n) return up[n-1].x; if(segcross(a, b, up[i], up[i-1])) { t = get_x(a, b, up[i], up[i-1]); if(dbcmp(t - ans) > 0) ans = t; } if(segcross(a, b, dn[i], dn[i-1])) { t = get_x(a, b, dn[i], dn[i-1]); if(dbcmp(t - ans) > 0) ans = t; } return ans; } int main() { //freopen("data.in", "r", stdin); int i, j; bool flag; double res; while(scanf("%d", &n), n) { for(i = 0; i < n; ++i) { scanf("%lf%lf\n", &up[i].x, &up[i].y); dn[i].x = up[i].x; dn[i].y = up[i].y - 1; } res = -inf; flag = true; for(i = 0; i < n && flag; ++i) { for(j = i + 1; j < n && flag; ++j) { res = max(res, solve(up[i], dn[j])); res = max(res, solve(dn[i], up[j])); if(dbcmp(res - up[n-1].x) >= 0) flag = false; } } if(flag) printf("%.2f\n", res); else puts("Through all the pipe."); } return 0; }
POJ 1408
纯属YY题,叉积求交点,叉积求多边形面积。然后取最大的面积就ok了。昨天调了一晚上,还以为是叉积求交点出错了呢,原来是求面积的时候算错三角形了,唉。。。T_T
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> using namespace std; const int M = 50; const double eps = 1e-8; const double inf = ~0u; struct point { double x; double y; point(double a = 0, double b = 0) : x(a), y(b) {} }; point a[M], b[M], c[M], d[M], p[M][M]; int dbcmp(double x) { if(x > eps) return 1; else if(x < -eps) return -1; return 0; } double det(double x1, double y1, double x2, double y2) { return x1*y2 - x2*y1; } double cross(point a, point b, point c) { return det(b.x - a.x, b.y - a.y, c.x - a.x, c.y - a.y); } point seg(point a, point b, point c, point d) { //叉积求交点 double s1, s2; s1 = cross(a, b, c); s2 = cross(a, b, d); point t; t.x = (c.x*s2 - d.x*s1)/(s2 - s1); t.y = (c.y*s2 - d.y*s1)/(s2 - s1); return t; } double area(point a, point b, point c, point d) { //叉积求面积 double ret = 0; ret = cross(a, b, c) + cross(a, c, d); return fabs(ret)/2.0; } void init(int n) { int i; for(i = 0; i < n; ++i) { scanf("%lf", &a[i].x); a[i].y = 0; } for(i = 0; i < n; ++i) { scanf("%lf", &b[i].x); b[i].y = 1; } for(i = 0; i < n; ++i) { scanf("%lf", &c[i].y); c[i].x = 0; } for(i = 0; i < n; ++i) { scanf("%lf", &d[i].y); d[i].x = 1; } } int main() { //freopen("data.in", "r", stdin); int n, i, j; double res = -inf, ta; point p1, p2, p3, p4; while(scanf("%d", &n), n) { init(n); res = -inf; p[0][0] = point(0, 0); p[n+1][n+1] = point(1, 1); p[0][n+1] = point(0, 1); p[n+1][0] = point(1, 0); for(i = 0; i < n; ++i) p[0][i+1] = c[i]; for(i = 0; i < n; ++i) p[n+1][i+1] = d[i]; for(i = 0; i < n; ++i) p[i+1][0] = a[i]; for(i = 0; i < n; ++i) p[i+1][n+1] = b[i]; for(i = 0; i < n; ++i) { for(j = 0; j < n; ++j) { p[i+1][j+1] = seg(a[i], b[i], c[j], d[j]); } } ++n; for(i = 0; i <= n; ++i) { for(j = 0; j <= n; ++j) { if(i + 1 <= n && j + 1 <= n) { ta = area(p[i][j], p[i+1][j], p[i+1][j+1], p[i][j+1]); if(dbcmp(ta - res) > 0) res = ta; } } } printf("%.6lf\n", res); } return 0; }
POJ 1584
题意:给出多边形顶点的个数,圆的半径,圆的x,y坐标,并且按顺时针(或逆时针)给出一个序列,表示一个多边形,判断这个多边形是不是凸包。如果是,判断圆是否在多边形内。。。
1、判凸包可以枚举没三个相邻的点a, b, c求叉积Pab×Pac。每一组Pab×Pac 都同号则说明多多边形是叉积。
2、判圆心是否在多边形每部。枚举相邻的两个点a,b,设圆心左边为c,然后同1,求叉积,判符号。
3、求圆心到多边形每条边的距离,与半径比较,如果小于半径则PEG WILL NOT FIT。设一条边的两个端点为a, b,圆心为c。这里可以用叉积求三角形Sabc的面积和ab的长度L。c到ab的距离就是Sabc/L。
ps:第一次提交的时候条件1判错了,wa了一次。T_T
#include <iostream> #include <cstring> #include <cstdio> #include <cmath> using namespace std; const int N = 1100; const double eps = 1e-8; struct point { double x; double y; }p[N], cir; double rad; int n; int dbcmp(double x) { if(x > eps) return 1; else if(x < -eps) return -1; return 0; } double det(double x1, double y1, double x2, double y2) { return x1*y2 - x2*y1; } double cross(point a, point b, point c) { //叉积 return det(b.x - a.x, b.y - a.y, c.x - a.x, c.y - a.y); } double dis(point a, point b, point c) { //求点到边的距离 double s, l; s = fabs(cross(c, a, b)); //Sabc * 2 l = sqrt((b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)); //L return s/l; } void solve() { double flag, t, d; int i; flag = cross(p[0], p[1], p[2]); for(i = 1; i < n-1; ++i) { //判是否为凸包 t = cross(p[i], p[i+1], p[i+2]); if(dbcmp(flag*t) < 0) { puts("HOLE IS ILL-FORMED"); return ; } } flag = cross(p[0], p[1], cir); d = dis(p[0], p[1], cir); if(dbcmp(rad - d) > 0) {puts("PEG WILL NOT FIT"); return ;} for(i = 1; i < n; ++i) { //判圆是否在多边形内并且圆心到直线的距离 <= radiu d = dis(p[i], p[i+1], cir); t = cross(p[i], p[i+1], cir); if(dbcmp(flag*t) < 0) { puts("PEG WILL NOT FIT"); return ; } if(dbcmp(rad - d) > 0) {puts("PEG WILL NOT FIT"); return;} } puts("PEG WILL FIT"); } int main() { //freopen("data.in", "r", stdin); int i; while(~scanf("%d", &n)) { if(n < 3) break; scanf("%lf%lf%lf", &rad, &cir.x, &cir.y); for(i = 0; i < n; ++i) { scanf("%lf%lf", &p[i].x, &p[i].y); } p[n] = p[0]; solve(); } return 0; }
POJ 2187
题意很清楚,求凸包的直径。表示不会旋转卡壳,只能暴力的来做。黑书上的Graham-Scan算法。O(n)的时间复杂度求凸包,然后对极点两两枚举,求最大距离。感觉这样写bug很明显,50000的数据如果都在凸包上,那。。。必定TLE!-_-! 丫的,第一次写的时候完了排序了。TLE*2 T_T!
渣代码:375+ms
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> using namespace std; const int N = 50007; const double eps = 1e-6; const double inf = ~0u; struct point { double x; double y; } p[N]; int st[N], f[N], n, t; bool vis[N]; int dbcmp(double x) { if(x > eps) return 1; else if(x < -eps) return -1; return 0; } bool cmp(point a, point b) { if((dbcmp(a.y - b.y) == 0)) return dbcmp(a.x - b.x) < 0; return dbcmp(a.y - b.y) < 0; } double det(double x1, double y1, double x2, double y2) { return x1*y2 - x2*y1; } double cross(point a, point b, point c) { return det(b.x - a.x, b.y - a.y, c.x - a.x, c.y - a.y); } double dis(point a, point b) { return (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y); } void graph(int dir) { //Graham求凸包 int i; t = 0; for(i = 0 ; i < n; ++i) { if(vis[i]) continue; while(t > 1 && cross(p[st[t-1]], p[st[t]], p[i])*dir < 0) --t; st[++t] = i; } for(i = 2; i < t; ++i) vis[st[i]] = true; } int main() { //freopen("data.in", "r", stdin); int i, m, j; double res, tmp; scanf("%d", &n); for(i = 0; i < n; ++i) { scanf("%lf%lf", &p[i].x, &p[i].y); } sort(p, p + n, cmp); memset(vis, false, sizeof(vis)); graph(1); for(i = 1; i <= t; ++i) f[i] = st[i]; m = t; graph(-1); for(i = 1; i < t; ++i) f[m+i] = st[t-i]; m += (t - 1); res = -inf; for(i = 1; i < m - 1; ++i) { for(j = i + 1; j < m; ++j) { tmp = dis(p[f[i]], p[f[j]]); if(tmp > res) res = tmp; } } printf("%.0f\n", res); return 0; }
POJ 1113
题意是给一组点的坐标表示的多边形,要从外围用绳子围起来,并且绳子到多边形的最短距离为L。求绳子的最短长度。
很明显,找出凸包,然后求各相邻极点间的距离,最后加上半径为L的圆的周长。
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> using namespace std; const int N = 1024; const double eps = 1e-6; const double pi = acos(-1.0); struct point { double x; double y; } p[N]; int st[N], t, n; bool vis[N]; int f[N]; int dbcmp(double x) { if(x > eps) return 1; else if(x < -eps) return -1; return 0; } bool cmp(point a, point b) { if(dbcmp(a.y - b.y) == 0) return dbcmp(a.x - b.x) < 0; return dbcmp(a.y - b.y) < 0; } double det(double x1, double y1, double x2, double y2) { return x1*y2 - x2*y1; } double cross(point a, point b, point c) { return det(b.x - a.x, b.y - a.y, c.x - a.x, c.y - a.y); } double dis(point a, point b) { return sqrt((b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)); } void graham(int dir) { int i; t = 0; for(i = 0; i < n; ++i) { if(vis[i]) continue; while(t > 1 && dir*cross(p[st[t-1]], p[st[t]], p[i]) < 0) --t; st[++t] = i; } for(i = 2; i < t; ++i) vis[st[i]] = true; } int main() { //freopen("data.in", "r", stdin); int i, m; double l, res; while(~scanf("%d%lf", &n, &l)) { for(i = 0; i < n; ++i) { scanf("%lf%lf", &p[i].x, &p[i].y); } sort(p, p + n, cmp); memset(vis, 0, sizeof(vis)); graham(1); for(i = 1; i <= t; ++i) f[i] = st[i]; m = t; graham(-1); for(i = 1; i < t; ++i) f[m + i] = st[t - i]; m += t-1; res = 0; for(i = 1; i < m; ++i) { res += dis(p[f[i]], p[f[i + 1]]); } res += 2*pi*l; printf("%.0lf\n", res); } return 0; }
ps:计算几何这几道水题终于刷完了,有的地方确实很恶心人。。。比如说POJ 1408,YY题,因为不熟,我敲了一个晚自习。。。发现计算几何的模板好多,记记模板,加油!^_^