POJ2187(凸包+旋转卡壳)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了POJ2187(凸包+旋转卡壳)相关的知识,希望对你有一定的参考价值。

  这道题目的大意是给出一组二维空间的顶点,计算其中距离最远的两个顶点之间的距离。

  先说明凸包的概念和求法。

  定义:对于多边形P,若将P中任意的两个点(包含边上)用一条线段连接,线段都落于该多边形中(含边),那么该多边形称为凸多边形。

  定义:点集Q的凸包是一个最小的凸多边形P,使得Q中的每个点都落于P中或P的边上。

  形象的看,可以将点集Q看作是一组钉在平面上的钉子,而利用一个橡皮圈将整个点集Q套入其中,使得橡皮圈绷直,那么橡皮圈实际上就是点集Q的凸包的外轮廓。

  假设点集Q的凸包为P,将P所有角按照逆时针排序,得到一组点序列q1,q2,...,qn。实际上P的所有角必定是Q中的点,否则可以通过旋转点的两边,使得两边交点为Q中的点,同时保证凸包性质。可以同样保证对于凸多变形,若其所有角被Q覆盖,那么必定是Q的凸包,因为我们没有办法继续缩减其大小。q1q3向量一定在q1q2向量的逆时针方向,否则连接q1和q2得到的向量将会落于凸包外。利用这个思路可以实现一个有效的Graham扫描算法,其可以在O(nlog2(n))的时间复杂度内建立点集的凸包,n为点集的大小。

  首先取Q中y坐标最小的点O(若有多个,则取其中x坐标最小的)。之后我们以O为原点建立极坐标系,可以发现所有其余顶点的极角都属于[0,180)。之后对其余顶点按照其极角从小到大排序(这里不需要真的计算角度,只需要利用向量的叉积判断向量之间的旋转关系即可),排序后,我们整合所有极角相同的顶点,只保留其中距离v最远的顶点。最后我们对排序的顶点集合L,进行下面的步骤

stack = empty-stack
stack.push(v)
for point in L
    while(stack.size() >= 2)
        if(cmul(stack.top1() - stack.top2(), point - stack.top2()) >= 0) //叉乘运算
            stack.pop()
        else
            break
    stack.push(point)

  最终保留在stack中的就是Q的凸包的按照逆时针排序的角。在算法一开始时,显然vUL中的顶点必定覆盖了凸包的所有角。在扫描每个点z时,若发现对于前面两个顶点y和x,有xz落在xy的顺时针方向或二者共线,那么从vUL中移除点y,依旧可以保证剩余顶点覆盖凸包所有角(注意v,x,z三个顶点组成的三角形已经包含了y)。因此到了最后stack中剩余的顶点必定覆盖了凸包的所有角。并且由于stack所有顶点都满足了不能被移除的性质,因此得到的就是凸包上按逆时针排序的角。

  时间复杂度为排序和上述扫描过程,排序为O(nlog2(n)),而扫描由于每个点最多一次入栈和一次出栈,而每次while循环都会发生一次出栈,因此while总共最多运行O(n)次,故扫描整个过程时间复杂度为O(n),总的时间复杂度取决于排序,为O(nlog2(n))。

  

  最远的两个顶点必定是落在凸包上的,若其中之一落在凸包内,那么可以将两个点相连,得到线段,并向两端扩展直到触碰到凸包轮廓,而这样的一条边是必定小于凸包上的某两个顶点的连线的,稍微运用一点高中几何的知识就可以证明。到了这里,问题还是没有解决,若问题给出所有顶点都是类似椭圆边上的顶点,那么凸包就将由所有顶点组成,这样计算凸包并不能简化对最远两个顶点之间距离的计算。

  可以使用旋转卡壳算法优化这一过程(Rotating Calipers Algorithm)。对于凸包上的某条边(a,b),对于所有凸包上的顶点v,若abc是所有类似的以ab为底的三角形中面积最大的,那么称a与v以及b与v均是对踵点。

  要找边(a,b)的对踵点,最直观的方式是对建立其平行线并找到凸包上另外一个与该平行线相切的交点(凸包上最多有两个切点,且这两个切点必定相连为边),该交点就是边(a,b)的对踵点。利用图形可以发现当我们选取下一条边(b,c)找寻其对踵点时(设a,b,c处于顺时针方向),(b,c)的对踵点必定为(a,b)的对踵点,或落于(a,b)的对踵点的顺时针方向。且对于任意一组边(a,b),以及b逆时针方向的顶点序列,c,d,e,...,可以发现其与(a,b)组成的三角形的面积是先增大后减小的,即存在一个极点,该极点即为(a,b)的对踵点。因此我们可以保证(b,c)的对踵点落于b的逆时针方向,且落于(a,b)的对踵点的顺时针方向,但绝不可能是b或c。因此当我们按顺时针扫描了凸包上的所有边后,我们对对踵点的查找最多绕了凸包两圈,即时间复杂度为O(n),n为凸包上的顶点数。

i = 2, j =0
n = convex.size()
convex[n] = convex[0]
result = empty-list
for( ; j < n; j++)
    while(area(convex[j], convex[j + 1], convex[i % n])
        < area(convex[j], convex[j + 1], convex[(i + 1) % n])) //计算面积差
        i += 1
    result.add(Pair(convex[j], convex[i % n]))
    result.add(Pair(convex[j + 1], convex[i % n]))
    if(area(convex[j], convex[j + 1], convex[i % n])
        == area(convex[j], convex[j + 1], convex[(i + 1) % n])) //边有两个对踵点
        result.add(Pair(convex[j], convex[(i + 1) % n]))
        result.add(Pair(convex[j + 1], convex[(i + 1) % n]))

  上面就是利用RC计算所有对踵点对的代码了。由于每条边最多带来4个对踵点对,因此可以保证结果中的对踵点对不会超过4n。

  但是说了这么多,计算对踵点对对于我们的问题有什么帮助呢?考虑a和b是最远的两个顶点,我们可以做两条平行线同时与凸包相切于点a和点b。之后旋转按任意方向旋转平行线,直到平行线于凸包的某个边重合。此时不妨设a为重合边的一端,显然此时b是a的对踵点。因此我们发现了凸包上的最远点对必定是对踵点,故我们只要遍历计算最多4n个对踵点对各自距离中的最大值,就是我们要的结果。

  到此累计的时间复杂度为O(nlog2(n))。

技术分享图片
  1 package cn.dalt.poj;
  2 
  3 import java.io.FileInputStream;
  4 import java.io.IOException;
  5 import java.io.InputStream;
  6 import java.util.*;
  7 
  8 /**
  9  * Created by dalt on 2017/12/11.
 10  */
 11 public class BeautyContest {
 12 
 13     static BlockReader reader;
 14 
 15     public static void main(String[] args) throws Exception {
 16         System.setIn(new FileInputStream("D:\\test\\poj\\BeautyContest.in"));
 17 
 18         reader = new BlockReader(System.in);
 19         while (reader.hasMore()) {
 20             BeautyContest beautyContest = new BeautyContest();
 21             beautyContest.init();
 22             System.out.println(beautyContest.solve());
 23         }
 24     }
 25 
 26     List<Vector2I> farmPositions;
 27 
 28     public void init() {
 29         int n = reader.nextInteger();
 30         farmPositions = new ArrayList();
 31         for (int i = 0; i < n; i++) {
 32             farmPositions.add(new Vector2I(
 33                     reader.nextInteger(), reader.nextInteger()
 34             ));
 35         }
 36     }
 37 
 38     public int solve() {
 39         Convex convex = Convex.makeConvex(farmPositions);
 40         if (convex.size() <= 2) {
 41             return GeomUtils.dist2(convex.getBottomLeftCorner(), convex.getTopRightCorner());
 42         }
 43 
 44         List<Vector2I[]> antipodalPairList = shamos(convex);
 45 
 46         int ret = 0;
 47         for (Vector2I[] pair : antipodalPairList) {
 48             ret = Math.max(ret, GeomUtils.dist2(pair[0], pair[1]));
 49         }
 50 
 51         return ret;
 52     }
 53 
 54     public static class Vector2I {
 55         final int x;
 56         final int y;
 57 
 58         public Vector2I(int x, int y) {
 59             this.x = x;
 60             this.y = y;
 61         }
 62 
 63         public Vector2I sub(Vector2I other) {
 64             return new Vector2I(x - other.x, y - other.y);
 65         }
 66 
 67 
 68         public String toString() {
 69             return String.format("(%d,%d)", x, y);
 70         }
 71 
 72 
 73         public boolean equals(Object obj) {
 74             Vector2I vec = (Vector2I) obj;
 75             return x == vec.x && y == vec.y;
 76         }
 77     }
 78 
 79     public static class BlockReader {
 80         InputStream is;
 81         byte[] dBuf;
 82         int dPos, dSize, next;
 83         static final int EOF = -1;
 84 
 85         public void skipBlank() {
 86             while (Character.isWhitespace(next)) {
 87                 next = nextByte();
 88             }
 89         }
 90 
 91         StringBuilder builder = new StringBuilder();
 92 
 93         public String nextBlock() {
 94             builder.setLength(0);
 95             skipBlank();
 96             while (next != EOF && !Character.isWhitespace(next)) {
 97                 builder.append((char) next);
 98                 next = nextByte();
 99             }
100             return builder.toString();
101         }
102 
103         public int nextInteger() {
104             skipBlank();
105             int ret = 0;
106             boolean rev = false;
107             if (next == ‘+‘ || next == ‘-‘) {
108                 rev = next == ‘-‘;
109                 next = nextByte();
110             }
111             while (next >= ‘0‘ && next <= ‘9‘) {
112                 ret = (ret << 3) + (ret << 1) + next - ‘0‘;
113                 next = nextByte();
114             }
115             return rev ? -ret : ret;
116         }
117 
118         public int nextBlock(char[] data, int offset) {
119             skipBlank();
120             int index = offset;
121             int bound = data.length;
122             while (next != EOF && index < bound && !Character.isWhitespace(next)) {
123                 data[index++] = (char) next;
124                 next = nextByte();
125             }
126             return index - offset;
127         }
128 
129         public boolean hasMore() {
130             skipBlank();
131             return next != EOF;
132         }
133 
134         public BlockReader(InputStream is) {
135             this(is, 1024);
136         }
137 
138         public BlockReader(InputStream is, int bufSize) {
139             this.is = is;
140             dBuf = new byte[bufSize];
141             next = nextByte();
142         }
143 
144         public int nextByte() {
145             while (dPos >= dSize) {
146                 if (dSize == -1) {
147                     return EOF;
148                 }
149                 dPos = 0;
150                 try {
151                     dSize = is.read(dBuf);
152                 } catch (IOException e) {
153                     throw new RuntimeException(e);
154                 }
155             }
156             return dBuf[dPos++];
157         }
158     }
159 
160     public static class GeomUtils {
161         private GeomUtils() {
162         }
163 
164         public static int dist2(Vector2I a, Vector2I b) {
165             int x = a.x - b.x;
166             int y = a.y - b.y;
167             return x * x + y * y;
168         }
169 
170         public static boolean sameLine(Vector2I a, Vector2I b, Vector2I c) {
171             return cmul(b.sub(a), c.sub(a)) == 0;
172         }
173 
174         public static int cmul(Vector2I a, Vector2I b) {
175             return a.x * b.y - a.y * b.x;
176         }
177 
178         public static int rectArea(Vector2I a, Vector2I b, Vector2I c) {
179             return Math.abs(cmul(a.sub(c), b.sub(c)));
180         }
181     }
182 
183     public static List<Vector2I[]> shamos(List<Vector2I> convex) {
184         //Rotating calipers
185         //Fetch all antipodal pair
186         int convexNum = convex.size();
187         int vertexIndex = 2;
188         convex = new ArrayList(convex);
189         convex.add(convex.get(0));
190         List<Vector2I[]> antipodalPairList = new ArrayList();
191         for (int i = 0; i < convexNum; i++) {
192             Vector2I edgePart1 = convex.get(i);
193             Vector2I edgePart2 = convex.get(i + 1);
194             while (true) {
195                 Vector2I scannedVector = convex.get(vertexIndex);
196                 Vector2I nextVector = convex.get(vertexIndex + 1);
197                 int areaDiff = GeomUtils.rectArea(edgePart1, edgePart2, scannedVector) - GeomUtils.rectArea(edgePart1, edgePart2, nextVector);
198                 if (areaDiff < 0) {
199                     if (++vertexIndex >= convexNum) {
200                         vertexIndex = 0;
201                     }
202                 } else {
203                     antipodalPairList.add(new Vector2I[]{edgePart1, scannedVector});
204                     antipodalPairList.add(new Vector2I[]{edgePart2, scannedVector});
205                     if (areaDiff == 0) {
206                         antipodalPairList.add(new Vector2I[]{edgePart1, nextVector});
207                         antipodalPairList.add(new Vector2I[]{edgePart2, nextVector});
208                     }
209                     break;
210                 }
211             }
212         }
213 
214         return antipodalPairList;
215     }
216 
217     public static class Convex extends AbstractList<Vector2I> {
218         private Vector2I[] vectors;
219         private Vector2I bl;
220         private Vector2I tr;
221 
222         public Vector2I getBottomLeftCorner() {
223             return bl;
224         }
225 
226         public Vector2I getTopRightCorner() {
227             return tr;
228         }
229 
230         @Override
231         public Vector2I get(int index) {
232             return vectors[index];
233         }
234 
235 
236         public int size() {
237             return vectors.length;
238         }
239 
240         public static Convex makeConvex(List<Vector2I> vector2IList) {
241             Convex result = new Convex();
242             if (vector2IList.size() == 0) {
243                 result.vectors = vector2IList.toArray(new Vector2I[0]);
244                 return result;
245             }
246 
247             //If all points located on same line
248             Vector2I v1 = vector2IList.get(0);
249             Vector2I v2 = vector2IList.get(0);
250             Vector2I bl = vector2IList.get(0);
251             Vector2I tr = vector2IList.get(0);
252             boolean sameLineFlag = true;
253             for (Vector2I vertex : vector2IList) {
254                 if (!GeomUtils.sameLine(v1, v2, vertex)) {
255                     sameLineFlag = false;
256                 }
257                 v2 = vertex;
258                 if (bl.y > vertex.y || (bl.y == vertex.y && bl.x > vertex.x)) {
259                     bl = vertex;
260                 }
261                 if (tr.y < vertex.y || (tr.y == vertex.y && tr.x < vertex.x)) {
262                     tr = vertex;
263                 }
264             }
265 
266             result.bl = bl;
267             result.tr = tr;
268             if (sameLineFlag) {
269                 if (bl.equals(tr)) {
270                     result.vectors = new Vector2I[]{bl};
271                 } else {
272                     result.vectors = new Vector2I[]{bl, tr};
273                 }
274                 return result;
275             }
276 
277             //Remove all inner vertex, make vectors contains points on outline of convex
278             //At first, sort by angle of vector bl-v
279             //v < u equals to that bl-u is on the anticlockwise of bl-v
280             //So we can simplify the procession of calculation because of -cmul(bl-v, bl-u)=v.compareTo(b)
281             final Vector2I finalBl = bl;
282             Vector2I[] vector2IListArray = vector2IList.toArray(new Vector2I[vector2IList.size()]);
283             vector2IList = Arrays.asList(vector2IListArray);
284             Arrays.sort(vector2IListArray, new Comparator<Vector2I>() {
285 
286                 public int compare(Vector2I a, Vector2I b) {
287                     int res = -GeomUtils.cmul(a.sub(finalBl), b.sub(finalBl));
288                     if (res == 0) {
289                         if (a.equals(finalBl)) {
290                             return -1;
291                         }
292                         if (b.equals(finalBl)) {
293                             return 1;
294                         }
295                     }
296                     return res;
297                 }
298             });
299             //Remove all the vertex has the same angle but retain the farthest one
300             int newSize = 1;
301             for (int i = 2, bound = vector2IList.size(); i < bound; i++) {
302                 Vector2I candidate = vector2IListArray[newSize];
303                 Vector2I scanOne = vector2IListArray[i];
304                 if (GeomUtils.sameLine(candidate, scanOne, bl)) {
305                     //Retain the farthest one in the vertexes with same angle
306                     //Replace the candidate
307                     if (GeomUtils.dist2(bl, scanOne) > GeomUtils.dist2(bl, candidate)) {
308                         vector2IListArray[newSize] = scanOne;
309                     }
310                 } else {
311                     //Add the candidate
312                     newSize++;
313                     vector2IListArray[newSize] = scanOne;
314                 }
315             }
316             vector2IList = vector2IList.subList(0, newSize + 1);
317             //Graham‘s Scan
318             LinkedList<Vector2I> stack = new LinkedList();
319             for (int i = 0, bound = vector2IList.size(); i < bound; i++) {
320                 Vector2I vec = vector2IList.get(i);
321                 while (stack.size() >= 2) {
322                     Vector2I top1 = stack.removeLast();
323                     Vector2I top2 = stack.getLast();
324                     if (GeomUtils.cmul(top1.sub(top2), vec.sub(top2)) > 0) {
325                         stack.addLast(top1);
326                         break;
327                     }
328                 }
329                 stack.addLast(vec);
330             }
331             result.vectors = stack.toArray(new Vector2I[stack.size()]);
332             return result;
333         }
334     }
335 }
View Code

  说真的,Graham很难写,需要考虑移除相同极角,考虑全顶点共线,重点等一大堆情况,恶心死了。

 

以上是关于POJ2187(凸包+旋转卡壳)的主要内容,如果未能解决你的问题,请参考以下文章

POJ2187 Beauty Contest (旋转卡壳算法 求直径)

poj 2187 Beauty Contest——旋转卡壳

POJ2187 Beauty Contest(旋转卡壳)

模板 旋转卡壳 poj2187

poj 2187旋转卡壳

poj2187最远点对(勉强凑数)