백준 10254 고속도로

https://www.acmicpc.net/problem/10254

2차원 평면 위에 n개의 도시들이 주어지면 n개의 도시 중 유클리드 거리가 가장 먼 두 도시를 찾는 문제이다. 컨벡스 헐로 점들을 감쌌을 때 점 사이의 최대 거리는 컨벡스 헐 꼭지점 사이에서만 존재할 수 있다는 개념을 활용한 문제이다.

여기서 로테이팅 캘리퍼스라는 알고리즘이 사용된다. 컨벡스 헐 상의 점들에 대해서 가장 먼 거리는 마치 캘리퍼스를 돌리듯이 확인하는 과정을 통해 쉽게 구할 수 있다는 알고리즘이다. 신기하게도 모든 점들간의 거리를 확인해주면 O(n2)의 시간이 걸리지만 로테이팅 캘리퍼스를 활용하면 O(n)만에 해결이 가능하다.

핵심 아이디어는 임의의 점을 잡았을 때 가장 먼 거리는 임의의 점과 맞다는 상태로 캘리퍼스로 도형을 돌린다고 했을 때 만나게 되는 점들 중에 하나라는 점이다.

10254

위의 그림은 로테이팅 캘리퍼스를 하는 과정 중 일부이다. 실제로 오각형 중 최하단 좌측에 위치한 점으로부터 가장 멀리있는 점의 거리는 첫번째 그림과 두번째 그림의 경우 중 하나에 해당한다. 자세히 보면 최하단 좌측 점은 항상 캘리퍼스 양 끝 선에 맞닿은 채로 확인하고 있다는 것을 알 수 있다.

최종적으로 모든 점들간의 거리를 확인해 줄 필요없이 임의의 점을 기준으로 본인과 가장 멀리있는 점들만 확인하게 된다. 따라서 한 점당 한번씩 방문을 하게되기 때문에 O(n)의 시간이 걸리게 된다.

파일의 입력이 크기때문에 fread를 해줘야 시간초과가 안난다. 관련 코드는 JCSooHwanCho님의 FileIO.swift 를 참조하였다. <출처 : https://gist.github.com/JCSooHwanCho/30be4b669321e7a135b84a1e9b075f88>

/*
 fread 코드는 JCSooHwanCho님의 FileIO.swift 를 참조하였습니다.
 <출처 : https://gist.github.com/JCSooHwanCho/30be4b669321e7a135b84a1e9b075f88>
*/

import Foundation

final class FileIO {
    private let buffer:[UInt8]
    private var index: Int = 0

    init(fileHandle: FileHandle = FileHandle.standardInput) {
        buffer = Array(try! fileHandle.readToEnd()!)+[UInt8(0)] // 인덱스 범위 넘어가는 것 방지
    }

    @inline(__always) private func read() -> UInt8 {
        defer { index += 1 }
        return buffer[index]
    }

    @inline(__always) func readInt() -> Int {
        var sum = 0
        var now = read()
        var isPositive = true

        while now == 10
                || now == 32 { now = read() } // 공백과 줄바꿈 무시
        if now == 45 { isPositive.toggle(); now = read() } // 음수 처리
        while now >= 48, now <= 57 {
            sum = sum * 10 + Int(now-48)
            now = read()
        }

        return sum * (isPositive ? 1:-1)
    }

    @inline(__always) func readString() -> String {
        var now = read()

        while now == 10 || now == 32 { now = read() } // 공백과 줄바꿈 무시
        let beginIndex = index-1

        while now != 10,
              now != 32,
              now != 0 { now = read() }

        return String(bytes: Array(buffer[beginIndex..<(index-1)]), encoding: .ascii)!
    }

    @inline(__always) func readByteSequenceWithoutSpaceAndLineFeed() -> [UInt8] {
        var now = read()

        while now == 10 || now == 32 { now = read() } // 공백과 줄바꿈 무시
        let beginIndex = index-1

        while now != 10,
              now != 32,
              now != 0 { now = read() }

        return Array(buffer[beginIndex..<(index-1)])
    }
}

func ccw(a: (Int, Int), b: (Int, Int), c: (Int, Int)) -> Int {
    let result:CLongLong = CLongLong(b.0-a.0)*CLongLong(c.1-a.1) - CLongLong(b.1-a.1)*CLongLong(c.0-a.0)
    return result > 0 ? 1 : (result == 0 ? 0 : -1)
}

func ccw2(a: (Int, Int), b: (Int, Int), c: (Int, Int), d: (Int, Int)) -> Bool {
    let u = (b.0-a.0, b.1-a.1)
    let v = (d.0-c.0, d.1-c.1)
    return ccw(a: (0, 0), b: u, c: v) >= 0
}

func dist(a: (Int, Int), b: (Int, Int)) -> CLongLong {
    let dx = CLongLong(a.0-b.0)
    let dy = CLongLong(a.1-b.1)
    return dx*dx+dy*dy
}

func convecs(arr: inout [(Int, Int)]) {
    var tmp = arr
    tmp.sort(by: {$0.1 < $1.1})
    tmp.sort(by: {$0.0 < $1.0})
    let startP = tmp[0]
    tmp[1..<tmp.count].sort(by: {
        ccw(a: startP, b: $0, c: $1) == 0 && (dist(a: startP, b: $0) < dist(a: startP, b: $1))
    })
    tmp[1..<tmp.count].sort(by: {ccw(a: startP, b: $0, c: $1) > 0})
    var stack:[(Int, Int)] = []
    for i in 0..<tmp.count {
        let next = tmp[i]
        while stack.count >= 2 && (ccw(a: stack[stack.count-2], b: stack[stack.count-1], c: next) <= 0) {
            stack.removeLast()
        }
        stack.append(next)
    }
    arr = stack
}

func calipers(arr: [(Int, Int)]) {
    let count = arr.count
    var pt = 0
    var ret:CLongLong = 0
    var xx = (0, 0), yy = (0, 0)
    for i in 0..<count {
        var now:CLongLong = 0
        while (pt+1 < count) && ccw2(a: arr[i], b: arr[i+1], c: arr[pt], d: arr[pt+1]) {
            now = dist(a: arr[i], b: arr[pt])
            if now > ret {
                ret = now
                xx = arr[i]
                yy = arr[pt]
            }
            pt += 1
        }
        now = dist(a: arr[i], b: arr[pt])
        if now > ret {
            ret = now
            xx = arr[i]
            yy = arr[pt]
        }
    }
    print(xx.0, xx.1, yy.0, yy.1)
}

let fIO = FileIO()
let t = fIO.readInt()
for _ in 0..<t {
    let n = fIO.readInt()
    var arr:[(Int, Int)] = Array(repeating: (0, 0), count: n)
    for i in 0..<n {
        arr[i] = (fIO.readInt(), fIO.readInt())
    }
    convecs(arr: &arr)
    calipers(arr: arr)
}