凸包类型的题算法主要有三种:JarvisMarch 算法、Graham 算法和 Andrew 算法,这三种算法时间性能上递增。
1. JarvisMarch 算法
1.1 思想
纵坐标最小然后横坐标最小的点一定是凸包上的点, 我们将其记为 p0,从 p0 开始,按逆时针的方向,逐个找凸包上的点,每前进一步找到一个点,所以叫作步进法。
- 选取下一个点的方法:
假设已找到 p0、p1,则利用跟 p0p1 向量夹角最小的点作为 p2。(p1 则利用 p0p1向量和水平线的夹角)
1.2 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
#include <queue> #include <cstdio> #include <cmath> #include <algorithm> using namespace std; const int MAXN = 10005; const double MAXD = 1e9; const double ACCUR = 1e-9; struct node { double x, y; bool operator < (const node n) const { if (abs(y-n.y) < ACCUR) return x < n.x; else return y < n.y; } bool operator == (const node n) const { return (abs(x-n.x) < ACCUR) && (abs(y-n.y) < ACCUR); } void operator = (const node n) { x = n.x; y = n.y; } }; struct vect { double x, y; void operator = (const vect v) { x = v.x; y = v.y; } double operator *(const vect v) const { return x*v.x + y*v.y; } }; bool equal (const double d1, const double d2) { return abs(d1-d2) < ACCUR; } vect vform(const node n1, const node n2) { vect tmpv; tmpv.x = n2.x - n1.x; tmpv.y = n2.y - n1.y; return tmpv; } double vlen(const vect v) { return sqrt(v.x*v.x+v.y*v.y); } double vcos(const vect v1, const vect v2) { return (v1*v2)/(vlen(v1)*vlen(v2)); } double area (const node n1, const node n2, const node n3) { double b1, b2, b3; b1 = vlen(vform(n1,n2)); b2 = vlen(vform(n2,n3)); b3 = vlen(vform(n3,n1)); double b = (b1+b2+b3)/2; return sqrt(b*(b-b1)*(b-b2)*(b-b3)); } node p[MAXN]; queue <node> bq; int main() { int n; while(scanf("%d", &n) == 1) { if(n == 0) { break; } int f[MAXN] = {0}; vect v; node p0, p1; p0.x = p0.y = MAXD; for (int i = 0; i < n; ++i) { scanf("%lf%lf", &(p[i].x), &(p[i].y)); if (p[i] < p0) { p0 = p[i]; } } p1 = p0; v.x = 1; v.y = 0; do { node p2; vect v1; int j; double minvcos = -1, minvlen = MAXD; for (int i = 0; i < n; ++i) { if (!f[i]) { vect tmpv; tmpv.x = p[i].x-p1.x; tmpv.y = p[i].y-p1.y; if (vcos(v,tmpv) > minvcos) { p2 = p[i]; v1 = tmpv; j = i; minvcos = vcos(v,tmpv); minvlen = vlen(tmpv); } else if (equal(vcos(v,tmpv),minvcos) && vlen(tmpv) < minvlen) { p2 = p[i]; v1 = tmpv; j = i; minvcos = vcos(v,tmpv); minvlen = vlen(tmpv); } } } bq.push(p2); p1 = p2; v = v1; f[j] = 1; }while(!(p1==p0));
double ans = 0; node fp, ep; fp = p0; while(!bq.empty()) { ep = bq.front(); bq.pop(); ans += vlen(vform(fp, ep)); fp = ep; } printf("%.2f\n", ans);
} return 0; }
|
2. Graham 算法
2.1 思想
把所有点放在二维坐标系中,则纵坐标最小的点一定是凸包上的点,记为 p0 。计算各个点相对 p0 的幅角,按从小到大的顺序对各个点排序。(当幅角相同是,距离 p0 比较近的排在前面)则幅角最小的点和最大的点一定在凸包上。取幅角最小的点记为 p1,将 p0、p1 入栈。连接栈顶的点和次栈顶的点,得到直线 l,看当前点是在直线的右边还是左边,在右边则栈顶元素不是凸包上的点,将其弹出,返回继续执行。如果在左边,则当前点是凸包上的点。一直到幅角最大的那个点为之。
- 叉积原理
两个向量的叉积 P1×P2=x1y2−x2y1,其中用结果的正负代表叉乘结果的方向。该公式本质是两个三维向量(z 轴分量为0)叉乘的结果(原来结果为(x1y2−x2y1)⋅k,其中 k 是 z 轴单位正向量)。
2.2 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
#include <stack> #include <cstdio> #include <cmath> #include <algorithm> using namespace std; const int MAXN = 10005; const double MAXD = 1e9; const double ACCUR = 1e-9; struct node { double x, y; bool operator < (const node n) const { if (abs(y-n.y) < ACCUR) return x < n.x; else return y < n.y; } bool operator == (const node n) const { return (abs(x-n.x) < ACCUR) && (abs(y-n.y) < ACCUR); } void operator = (const node n) { x = n.x; y = n.y; } }; struct vect { double x, y; void operator = (const vect v) { x = v.x; y = v.y; } double operator *(const vect v) const { return x*v.y - y*v.x; } }; node p0; bool equal (const double d1, const double d2) { return abs(d1-d2) < ACCUR; } vect vform(const node n1, const node n2) { vect tmpv; tmpv.x = n2.x - n1.x; tmpv.y = n2.y - n1.y; return tmpv; } double vlen(const vect v) { return sqrt(v.x*v.x+v.y*v.y); } double vcos(const vect v1, const vect v2) { return (v1*v2)/(vlen(v1)*vlen(v2)); }
bool cmpp (const node p1, const node p2) { vect v1, v2; v1 = vform(p0, p1); v2 = vform(p0, p2); if (equal(v1*v2,0)) { return vlen(v1) < vlen(v2); } else { return v1*v2 > 0; } }
bool cmpv (const vect v1, const vect v2) { return (v1*v2 > 0) || equal(v1*v2,0); } double area (const node n1, const node n2, const node n3) {
vect v1, v2; v1 = vform(n1, n2); v2 = vform(n1, n3); return abs(v1*v2)/2; } node p[MAXN]; stack <node> bs; int main() { int n; p0.x = p0.y = MAXD; scanf("%d", &n); for(int i = 0; i < n; ++i) { scanf("%lf%lf", &(p[i].x), &(p[i].y)); if(p[i] < p0) { p0 = p[i]; } } sort(p,p+n,cmpp); bs.push(p[0]); bs.push(p[1]); int j = 2; while(j < n) { node p1, p2; p2 = bs.top(); bs.pop(); p1 = bs.top(); vect v1, v2; v1 = vform(p1,p2); v2 = vform(p2,p[j]); if(cmpv(v1,v2)) { bs.push(p2); bs.push(p[j]); ++j; } }
double ans = 0; node fp, np, ep; fp = bs.top(); bs.pop(); np = bs.top(); bs.pop(); while(!bs.empty()) { ep = bs.top(); bs.pop(); ans += area(fp,np,ep); np = ep; } printf("%d\n", (int)ans/50); return 0; }
|
3. Andrew 算法
3.1 思想
预处理排序改为水平排序,按照横坐标从小到大进行排序,横坐标相同则按纵坐标从小到大排。按照 graham 算法思想从 p0、p1 扫描所有点得到下凸包,再从 pn−1、pn−2 扫描所有点得到上凸包,二者结合即为整个凸包。(注意:这里的 p1 不一定在凸包里)
3.2 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
#include <stack> #include <cstdio> #include <cmath> #include <algorithm> using namespace std; const int MAXN = 10005; const double MAXD = 1e9; const double ACCUR = 1e-9; struct node { double x, y; bool operator < (const node n) const { if (abs(x-n.x) < ACCUR) return y < n.y; else return x < n.x; } bool operator == (const node n) const { return (abs(x-n.x) < ACCUR) && (abs(y-n.y) < ACCUR); } void operator = (const node n) { x = n.x; y = n.y; } }; struct vect { double x, y; void operator = (const vect v) { x = v.x; y = v.y; } double operator *(const vect v) const { return x*v.y - y*v.x; } }; bool equal (const double d1, const double d2) { return abs(d1-d2) < ACCUR; } vect vform(const node n1, const node n2) { vect tmpv; tmpv.x = n2.x - n1.x; tmpv.y = n2.y - n1.y; return tmpv; }
double vlen(const vect v) { return sqrt(v.x*v.x+v.y*v.y); }
double vcos(const vect v1, const vect v2) { return (v1*v2)/(vlen(v1)*vlen(v2)); }
bool cmpv (const vect v1, const vect v2) { return (v1*v2 > 0) || equal(v1*v2,0); } double area (const node n1, const node n2, const node n3) {
vect v1, v2; v1 = vform(n1, n2); v2 = vform(n1, n3); return abs(v1*v2)/2; } node p[MAXN]; stack <node> bs; int main() { int n; scanf("%d", &n); for(int i = 0; i < n; ++i) { scanf("%lf%lf", &(p[i].x), &(p[i].y)); } sort(p,p+n); bs.push(p[0]); bs.push(p[1]); int j = 2; while(j < n) { node p1, p2; p2 = bs.top(); bs.pop(); if(bs.empty()) { bs.push(p2); bs.push(p[j]); ++j; } else { p1 = bs.top(); vect v1, v2; v1 = vform(p1,p2); v2 = vform(p2,p[j]); if(cmpv(v1,v2)) { bs.push(p2); bs.push(p[j]); ++j; } } } int k = n-2; while(k >= 0) { node p1, p2; p2 = bs.top(); bs.pop(); p1 = bs.top(); vect v1, v2; v1 = vform(p1,p2); v2 = vform(p2,p[k]); if(cmpv(v1,v2)) { bs.push(p2); bs.push(p[k]); --k; } } bs.pop();
double ans = 0; node fp, np, ep; fp = bs.top(); bs.pop(); np = bs.top(); bs.pop(); while(!bs.empty()) { ep = bs.top(); bs.pop(); ans += area(fp,np,ep); np = ep; } printf("%d\n", (int)ans/50); return 0; }
|