1. 程式人生 > >POJ2187(凸包+旋轉卡殼)

POJ2187(凸包+旋轉卡殼)

nts ins rri 建立 put str 遠的 ini 發生

  這道題目的大意是給出一組二維空間的頂點,計算其中距離最遠的兩個頂點之間的距離。

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

  定義:對於多邊形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(凸包+旋轉卡殼)