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(凸包+旋轉卡殼)