ICPC Asia-Pacific Championship J
난이도 (체감): Gold 2?
문제 요약: 정점과 간선이 $10^5$, $3 \times 10^5$ 개 이하인 그래프 $G$에 대하여, 1번 정점에서 $n$번 정점에 갔다가 다시 돌아오려고 한다. 이때, 가는 경로 와 오는 경로 는 사용하는 간선의 집합으로 볼 때 달라야 한다. 최단 경로를 찾아라.
이런 류 문제는 대부분 그래프를 적절히 변형하여 모델링한 다익스트라 가 됩니다. 비슷하게 풀 수 있습니다.
먼저, 1번에서 $n$번으로 가는 최단 경로 $P$가 있다면, 가는 경로를 $P$로 고정해도 상관없음을 관찰합니다. 만약 가는 경로와 오는 경로가 모두 $P$와 다르다면, 그중 하나를 $P$로 바꾸어도 손해가 발생하지 않습니다. 가는 경로와 오는 경로를 맞바꾸어도 상관없으므로, $P$로 $n$에 도달했다고 생각해도 상관 없습니다.
이제, $P$와 다르면서 최단인 경로를 찾아야 합니다. 정점 $v$들마다 새로운 정점 $v’$를 생각하고, 간선 $(u, v, t)$에 대해 (실제로는 양방향입니다)
그래프를 생각합니다. 이는 그래프를 위와 아래 두 층으로 나누어서, 위-그래프와 아래-그래프 각각이 $G$와 똑같이 생겼으면서, $P$에 포함되지 않은 간선들만 이용하여 아래-그래프로부터 위-그래프로 이동할 수 있도록 한 셈이 됩니다.
이제, 아래-그래프의 $n$번에서 출발하여 위-그래프의 $1$번인 $1’$ 으로 이동하는 최단 경로를 찾으면 됩니다.
비슷한 문제는 정말 흔한것 같습니다. 경로의 홀짝성, 특정 점을 밟았는지 여부 등 다양한 최단 경로 문제에 이용됩니다.
ICPC Asia-Pacific Championship E
난이도 (체감): Platinum IV?
문제 요약: $n \times n$ 행렬에 1부터 $n$까지의 수가 쓰여 있다. 이때, 최소 개수의 수를 바꾸어서, 모든 행과 모든 열에 중복된 수가 있도록 하라.
중복된 수가 없는 행 또는 열을 슬프다 고 하겠습니다. (Sad? ㅋㅋㅋㅋㅋㅋ 문제풀때 실제로 이런식으로 말했던거 같은데, 공식 해설은 Colorful 같은 행복한 용어를 사용합니다.)
$r$번째 행과 $c$ 번째 열이 모두 슬픈 경우, $(r, c)$ 칸의 원소를 아무것으로나 바꾸어줌으로써 양쪽의 슬픔을 동시에 해소할 수 있습니다. (가능한 수가 $1$부터 $n$까지밖에 없기 때문에, 어떤 행/열이 슬프다면 항상 1부터 $n$까지의 수가 정확히 한 번씩 사용되고 있기 때문입니다)
$r$번째 행이 슬프지만 $c$번째 열이 슬프지 않은 경우, $(r, c)$ 칸의 원소를 적절히 바꾸어야 합니다.
적절히 바꾸는 것만 조심한다면, 행렬 전체의 슬픔은 매번 2 또는 1만큼씩 줄어듭니다. Priority Queue 등의 적절한 자료구조를 이용하여 2만큼 줄일 수 있을때 항상 그리한다면 최소 횟수만에 해결할 수 있습니다.
공식 해설에는 최적성에 대한 더 깔끔한 증명이 있습니다. 일반성을 잃지 않고 슬픈 열의 개수 $m_c$가 슬픈 행의 개수 $m_r$보다 적다고 하면, 항상 열의 슬픔을 교정하는 식으로 접근합니다. 그러나 이때 첫 $m_r$번은 행과 열을 동시에 교정할 수 있습니다. 이렇게 하면 $min(m_r, m_c)$ 번이 됩니다.
ICPC Asia-Pacific Championship F
난이도 (체감): Platinum III?
문제 요약: 2번부터 $n$번까지 $n-1$명의 사람이 한줄로 늘어서 있고, 각각은 $a_i$ 의 실력 을 가지고 있다.
이때, 팀장을 줄의 원하는 위치로 움직일 수 있고, 팀의 개수 $k$를 선택할 수 있다.
선택한 후에는 (팀장이 들어간 위치를 고려하여, 새로 번호를 매긴) $(1, k+1, 2k+1 \dots)$가 1번 팀이 되고, $(2, k+2, \dots)$ 가 2번 팀이 되는 식으로 팀이 이루어진다.
팀의 실력은 팀원의 실력의 합이라고 할 때, 최대한 공정하게 (팀 실력의 최대/최소의 비율로) 팀장을 배치하고, $k$를 고르는 방법을 제시하라.
팀장을 배치하는 것과 팀의 수를 선택하는 두가지 작업을 할 수 있다는 점이 어렵습니다.
먼저, $k$가 고정되어 있다고 치고 팀장의 위치만 움직여 가며 문제를 푼다고 생각해 봅니다. 배치가 결정되면 $O(n)$ 시간에 각 팀의 실력을 계산할 수 있지만, 계산해야 할 자리가 $n$개이기 때문에 이 방법으로는 복잡도를 맞출 수 없습니다. 그러나, 다음 관찰로 복잡도를 줄일 수 있습니다.
관찰: 팀장을 오른쪽으로 한 칸 옮길 때, 소속된 팀이 바뀌는 사람은 팀장과 방금 자리를 바꾼 사람 두명밖에 없다.
팀 $k$개의 실력을 적절한 자료구조를 이용하여 유지한다면, 최대 두 팀만 실제로 실력값이 바뀌게 되고, 그 과정에서 최대와 최소를 유지해야 하므로 $O(\log k)$ 시간에 팀장을 한 칸 옮겨볼 수 있습니다. $k \leq n$ 이므로, 이는 즉 팀장을 맨 왼쪽부터 맨 오른쪽까지 옮기면서 최적배치를 계산하는 데 $O(n \log n)$ 시간을 쓴다는 것입니다.
이제, $n$의 각 약수 $k$에 대해 따로 문제를 푼다고 생각하고, 전체 복잡도를 계산해 봅시다. $n$의 약수의 개수를 $d(n)$ 이라고 쓸 때, \(\sum_{k \di n} n \log k = O(d(n) \times n \log n)\) 입니다. ($\log k$를 타이트하게 잡아도, $\sum_{k \di n} \log k$ 는 $(d(n) \log n) / 2$ 이므로 변하지 않습니다) $d(n)$은 꽤 작은 값이지만, $n = 10^6$ 이하에서 $d(n)$의 최댓값은 무려 240임을 (720,720이 240개의 약수를 가집니다) 생각하면 시간상 무리해 보입니다.
따라서, 다음의 관찰을 통해 복잡도를 더 줄여야 합니다.
관찰: 팀의 개수는 반드시 $n$의 소인수일 때 최적이다.
이는 임의의 정수 $m > 1$에 대해, $k$팀을 만드는 것보다 $mk$팀을 만드는건 항상 손해임을 보이면 됩니다. 증명은 비교적 간단한데, formal하게 작성하기보다는 2팀을 만들지 4팀을 만들지의 예시를 들어 생각해보겠습니다. 4팀 상태에서 팀 4개를 강한 순서대로 나열하면, 2팀으로 팀을 줄일때 1번과 3번, 2번과 4번 팀이 묶이게 됩니다. 이때 강한쪽 팀은 1번팀의 2배보다 약하고, 약한쪽 팀은 4번팀의 2배보다 강해지기 때문에 강한 팀과 약한 팀의 실력 비율은 더 공정해집니다.
따라서, $n$의 모든 약수가 아닌 소인수들에 대해서만 풀면 되고, 100만 이하의 수는 최대 소인수가 7개뿐이므로 $n \log n$의 상수배 (7) 만에 해결할 수 있습니다.
BOJ 3611 팀의 난이도 / 2006 ICPC Northen Eurasia Finals H
난이도: Diamond II
문제 요약: 최대 정점 100개, 간선 1000개의 그래프에서, Densest Subgraph 찾기.
(정점의 부분집합을 골라서, induced subgraph의 density를 최대화하라)
사실 PS에 이런 문제를 낸 저의까지는 잘 모르겠습니다만, TCS / DB / DM 모두에서 유명하고 실제 research도 많이 된 문제입니다. Wikipedia에 이 문제에 대한 글도 있습니다.
아마도 출제 의도는 Goldberg나 Tarjan의 flow 기반한 알고리즘이었던 것으로 보입니다. 이 문제는 적절하게 min-cut 문제로 모델링할 수 있고, 이를 flow로 해결할 수 있으므로…
다만 저는 이 문제를 PS하다가 본게 아니라, 연구실에서 관련 논문을 통해 접했습니다. 최근 제시된 구현하기 비교적 어렵지 않은 approximation 알고리즘 [1]이 있어서 구현해보고 제출했습니다. 기본적인 아이디어는 Core Decomposition (나중에 언젠가 소개할 기회가 있을 것 같습니다) 을 수행하면 dense한 영역을 대강 찾아준다는 것에 착안하여 이를 반복하는 것인데, 나중에 이론적으로도 정확한 approximation ratio에 대한 증명이 이루어졌습니다 [2].
이외에도 이 문제를 비롯하여 관련한 여러 문제에 대한 연구가 활발히 이루어지고 있습니다. 여전히 PS 대회에서 flow 알고리즘이 생각해낼만 한지는 잘 모르겠습니다.
Digvijay Boob et al., Flowless: Extracting Densest Subgraphs Without Flow Computations, Proceedings of ACM Web Conference, 2020
Kent Quanrud Chandra Chekuri, and Manuel R. Torres, Densest Subgraph: Supermodularity, Iterative Peeling, and Flow, Proceedings of the Annual ACM-SIAM Symposium on Discrete Algorithms, 2022
Atcoder Beginner 331F
난이도: 1666 (Atcoder)
문제 요약: 문자열 $S$에 대해, 다음 두 쿼리가 주어진다. (문자열 길이 $10^6$, 쿼리 개수 $10^5$)
1 x c
: $S$의 $x$ 번째 문자를 $c$로 바꾼다2 l r
: $S$의 $[l, r]$ 부분 문자열이 Palindrome인지 판정하라.1번 쿼리 (문자열 업데이트) 가 없다면 당연히 Manacher’s algorithm으로 선형 시간에 전처리하고 쿼리당 $O(1)$에 답할 수 있습니다.
그러나, Manacher’s algorithm은 문자열 업데이트에 대응할 수 없습니다. 대신에, 이 문제의 경우 Rabin-Karp 해싱을 이용하면 됩니다.
Rabin-Karp 해싱에서, 문자열 $a_0\dots a_{L-1}$은 $\sum_{i = 0}^{L-1} a_i x^i \mod M$ 으로 해싱됩니다. (이때 $M$ 으로는 일반적으로 소수를 사용하고, $x$는 $M$과 서로소이면 큰 문제가 없는 것으로 알고 있습니다. 저는 습관적으로 29 (대소문자가 있으면 61), 10억7 정도의 $x$와 $M$을 씁니다.)
이러한 해싱을 이용하면, 문자열 $A$와 $B$가 주어졌을 때, $AB$ concatenation 의 해시값을 $h(A) \times p^{\text{len}(B)} + h(B)$ 로 $O(1)$에 빠르게 계산할 수 있습니다. ($p^{\text{len}(B)}$ 를 계산하는 데 원래는 $\log$ 시간이 걸리지만, 이것은 전처리해 놓는다고 생각하고)
따라서, Segment tree를 만들어서, 각 노드가 자신이 담당하는 부분문자열의 해시값 을 가지고 있게 한다면, 이 값을 빠르게 업데이트할 수 있습니다.
이제, 실제로는 팰린드롬인지를 판정해야 하므로, $S$와 $\text{reverse}(S)$ 에 대한 segment tree를 각각 만듭니다.
해시 충돌이 약간 걱정되지만, 설마 그렇게 사악하겠느냐 는 믿음을 가지고 제출했습니다.
Atcoder Beginner 331F
난이도: 1770 (Atcoder)
문제 요약: $1, 2, \dots N$ 번 사람이 줄에 서 있을 때, 맨 앞 사람에 대해 다음 ‘시행’ 을 반복한다.
맨 앞 사람을 $1/2$ 확률로 제거하고, 만약 제거되지 않았다면 맨 뒤로 보낸다.
이 시행을 한 사람만이 남아 있을 때까지 계속 반복할 때, $i$번째 사람이 마지막으로 남아 있게 될 확률을 구하시오.
대회중에는 약간의 실수로 해결하지 못했지만, 재밌는 확률 DP 문제라고 생각했습니다.
$D(i, j)$ 를 $i$명이 줄에 남은 채로 $(1 \dots i)$ 이 작업을 시작할 때, 이때의 $j$번째 사람이 마지막으로 남는 사람일 확률이라고 정의합니다. 이제, 우리가 원하는 값은 $D(N, j)$ 들을 구하면 됩니다.
$D(i, 1)$ 을 구하는 과정을 생각해보면, $1/2$ 확률로 이 사람이 사라지고, $1/2$ 확률로 줄의 맨 뒤로 가게 됩니다. 이는 즉, \(D(i, 1) = \frac{1}{2} D(i, i)\) 식이 항상 성립합니다. 이제, 나머지 $D(i, j)$ 를 생각해보면, $j$번째 사람이 마지막 사람이기 위해서는
BOJ 18180 / ICPC Central European Regional Contest 2019 S
난이도: Platinum II
문제 요약: 정점과 간선이 $10^5$ 개 이하인 그래프에 대하여, 다음의 쿼리가 최대 $10^5$개 주어진다.
단, 주어지는 집합 $S$의 크기의 합은 $10^5$ 이하이다.
굉장히 간단하지만, 복잡도를 맞추기 쉽지 않습니다. 정점 집합 하나에 대해서라면 union-find나 DFS를 이용하여 선형 시간에 판정할 수 있지만, 쿼리를 처리하기는 어려워 보입니다.
관점을 달리하여, 선형 시간에 충분히 큰 집합도 해결할 수 있다 라고 생각해 봅시다. 반대로, 작은 집합이라면 모든 정점의 쌍들을 확인해 볼 수 있습니다. 즉, 어떤 $K$를 잡아서,
주어지는 쿼리의 크기를 $s_1 \dots s_Q$ 라 할 때, $\sum s_i \leq 10^{5}$ 이므로,
첫번째 경우는 아무리 커도 $\sum_{i = 1}^{Q} s_i^2 \alpha(V) \leq K \times 10^5 \times \alpha(V)$ 를 넘지 않고,
두번째 경우는 쿼리가 최대 $10^5 / K$ 개밖에 없으므로 연산량을 대략 $E \times 10^5 \times \alpha(V) / K$ 로 바운드할 수 있습니다.
따라서, $K = 10^{2.5}$ 정도로 잡으면 양쪽 모두를 $10^{7.5}$ ($\alpha(V)$ 항을 대충 무시하고) 정도로 맞춰볼 수 있습니다.
일종의 sqrt decomposition 처럼 복잡도를 맞추는 이런 류의 트릭은 비교적 간단하면서도 굉장히 강력한것 같습니다. :)
For some reason, 유럽권은 동유럽이든 서유럽이든 알고리즘 하시는 분들이 geometry를 매우 사랑하는것 같습니다. 기하문제 두개가 있었지만 손도 대지 못했습니다 :( ICPC에서 기하문제의 비율이 다른 리저널보다 높은것만이 아니라, 묘하게 연구 쪽에서도 computational geometry가 강합니다.
STOC (Symposium on Theory Of Computing) SODA (Symposium On Discrete Algorithms) SoCG (Symposium on Computational Geometry), 세개의 top conference에서 가장 많은 논문을 publish한 학교/연구소들인데, 왼쪽의 두개 (STOC/SODA) 는 알고리즘과 계산이론 전반을 다루고 오른쪽 SoCG는 계산기하에 집중하는 학회입니다. SoCG에 유독 유럽권 기관들이 눈에 띕니다 (Free U of Berlin, Inria Sophia, Utrechet, Max Planck, ETH Zurich…)
BOJ 18171 / ICPC Central European Regional Contest 2019 A
난이도: Platinum IV
문제 요약: 문자열 $S$에 대해, 최소 개수의 글자를 뒤에 덧붙여서 문자열을 팰린드롬으로 만들려고 한다. 몇 글자가 필요한가?
문자열 $S$가 어떤 문자열 $A$ 와 팰린드롬 $P$ 에 대해 $S = A + P$ 라면, $\abs{A}$ 만큼을 뒤에 덧붙여 $A + P + rev(A)$ 를 만들면 됩니다.
이러한 $A$가 최대한 짧아야 하므로, 반대로 $P$가 최대한 길어야 합니다. 따라서, Manacher’s Algorithm으로 $S$의 모든 극대 팰린드롬 부분문자열을 찾고, 그것들 중 $S$의 맨 뒤까지 닿는 최대 길이의 팰린드롬이 무엇인지를 판정하면 충분합니다. $O(\abs{S})$ 에 해결할 수 있습니다.
BOJ 16700 / ICPC Central European Regional Contest 2018 I
난이도: Platinum III
문제 요약: 격자점 그리드상에, 두 가지 종류의 쿼리가 주어진다. 처음에는 모든 점이 ‘밟을 수 없는’ 점이다.
1 x1 y1 x2 y2
: $[x_1, y_1] \times [x_2, y_2]$ 직사각형 상의 격자점들을 ‘밟을 수 있게’ 한다.2 x1 y1 x2 y2
: $(x_1, y_1)$ 에서 $(x_2, y_2)$ 까지, ‘밟을 수 있는’ 점들만 통해 이동할 수 있는지 판정하라.$R \times C$ 칸의 Union-Find를 유지할 수 있다면, 쉽게 풀 수 있습니다. 여기서 문제는 1번 쿼리 한번에 $O(RC)$ 시간을 써서는 복잡도를 맞출 수 없다는 점입니다.
한개당 $C$칸을 관리하는, $R$개의 Union find 자료구조를 추가로 관리하면, 이미 합쳐진 segment를 건너뛰면서 합칠 수 있습니다.
이 테크닉은 2022년 서강대학교 프로그래밍 대회 에서 저는 (검수하면서) 처음 알게 되었는데, 꽤 재밌는 테크닉인것 같습니다. 잘 알려져 있는지는…잘 모르겠습니다.
여담으로, Path compression’만’ 사용하면, 쿼리당 amortized $O(\log n)$ 시간에 작동함이 알려져 있습니다. PS 세팅에서, $\log n$ 와 $\alpha(n)$을 구분해낼수있는 문제가 있는지는 잘 모르겠습니다. 어쩌면 모든 UF문제를 사실 path compression 하나로 뚫을수 있을지도 모르겠습니다.
BOJ 13952 / ICPC Central European Regional Contest 2016 H / 2016-2017 Grand Prix of Europe
난이도: Diamond V
문제 요약: 일부 칸들이 막혀있는 2차원 그리드가 주어진다. 한 변의 길이가 홀수 $k$인 정사각형 박스를 중심의 위치가 $(r_1, c_1)$ 에서 $(r_2, c_2)$ 까지 옮길 수 있는지 판정하여라. 단, 그리드의 크기는 $1000 \times 1000$ 이고, $(r_1, c_1), (r_2, c_2)$ 쿼리는 $300,000$개 주어진다.
쿼리 형태라는것이 난이도의 메인 요소라서 요약에서도 명시했습니다.
변의 길이가 홀수인 박스가 있을 때, 이 박스가 $(r, c)$를 밑면의 중심으로 한다면 이를 “$(r, c)$ 위에 있다” 라고 부르겠습니다. 이하, $N$ 은 그리드의 변길이 (1000), $Q$는 쿼리의 개수 (30만) 입니다.
쿼리가 한 개만 주어진다고 생각하면, 다음과 같이 문제를 풀 수 있습니다.
즉, 이 작업은 $O(N^2)$ 시간에 모두 수행할 수 있습니다. 그러나 $O(N^2 Q)$ 로는 시간제한을 통과할 수 없습니다.
시간 복잡도를 낮추기 위해서는, 병렬 이분 탐색을 생각할 수 있습니다. 1) 쿼리 문제이고, 각 쿼리를 오프라인으로 처리할 수 있으며 2) 각 쿼리를 이분탐색으로 처리할 수 있기 때문입니다.
병렬 이분 탐색에 대해서는 kks227님의 블로그 글 이 매우 쉽게 소개하고 있습니다. 요점은 각 쿼리에 대해 [lo, hi] 값을 관리하면서, 한번 도는 사이에 모든 쿼리에 대해 업데이트하는 것입니다.
한 쿼리를 이분 탐색하는 방법을 생각해 봅시다. 큰 박스가 올라갈 수 있는 (값이 큰) 칸부터 추가하면서,
여기에 병렬 이분 탐색을 적용하여 전체를 $O(QN\log N)$ 시간에 풀 수 있고, 이 값이 조금 커 보이지만 제한시간이 8초이므로 통과할 수 있습니다.
BOJ 7911 / Poland Collegiate Programming Contest (AMPPZ) 2011F.
난이도: Platinum IV
문제의 서술이 매우 혼란스럽습니다. 스토리가 문제의 해석을 방해하는 전형적인 경우가 아닌가 싶은데… 아래에는 문제의 수학적인 formulation을 유지하면서 스토리를 단순화하여 기술합니다.
문제 요약: 수열 $d_1, \dots d_n$에 대해, $a_i = 2d_i, b_i = 3d_i$ 라 하자. 1번부터 $K$번 색까지의 페인트가 각각 $l_1 \dots l_K$ 만큼 있고, 이것을 이용하여 $a_i$ 들과 $b_i$ 들에게 색깔을 칠하고자 한다. 최소 종류의 페인트를 사용하면서 다음의 조건을 만족하도록 할 때, 필요한 페인트는 최소 몇 종류인가?
다음과 같은 그리디 알고리즘으로 해결할 수 있습니다. 처음에는 $a_i$ 와 $b_i$ 를 합쳐서 $i$번 작업으로 생각합니다.
BOJ 6672 / CTU Open Contest 2004E.
난이도: Platinum III
문제 요약: 그래프 $G$가 주어진다. 이때, 한 정점(과 그 정점에 이어진 간선들)을 삭제하여, 그래프의 Connected component의 개수를 최대화하라.
Note: :angry: 예제가 정말 아무런 도움이 되지 않습니다. :rage:
단절점 알고리즘은 대략 다음과 같습니다. DFS tree를 만들면서 방문시간을 기록하는데,
주의: 간선이 0개인 경우, 정점은 하나를 지워야 하므로 (정점 개수) 가 아니라 (정점 개수 - 1)이 답이 됩니다. 예제에 이것마저 없었다면 아무도 맞추지 못했을 것 같습니다. :rage:
BOJ 7981 / Poland Collegiate Programming Contest (AMPPZ) 2012H / 경기과학고 나는코더다 송년대회 2016F
난이도: Platinum II
이렇게 한문제에 전혀다른 출처가 두개 이상 달리는게 간혹 있던데, 문제를 가져다 쓴건지 뭔지 잘 모르겠네요.
문제 statement가 충분히 concise하게 잘 쓰여져 있어, 원문으로 충분한 것 같아 바로 풀이만 작성합니다.
Dhdroid가 알려주지 않았다면 아마 해결하기 힘들었을것 같습니다. solved.ac 난이도에 비해 훨씬 어렵다고 생각합니다.
약한충격의 코스트 $u_i$ 와 강한충격의 코스트 $z_i$에 더하여, “진짜 최소 코스트” $x_i$ 가 있다고 생각하겠습니다. 다음을 관찰하는 것이 매우 중요합니다.
시간복잡도는 장비의 개수 $N$과 간선개수 ($r_i$들의 합) $R$에 대해, $O(N \log N + R)$ 시간에 구현할 수 있습니다.
BOJ 3413 / ICPC Central European Regional Contest 2012 I
난이도: Platinum I
문제 요약: 평면상에 $N$개의 직선과 $M$ 개의 점이 주어질 때, 직선으로 인해 나누어지는 평면의 영역 하나당 점이 적어도 한개씩 들어있는지 여부를 판정하라.
직선 $N$개가 주어졌을 때, 이로 인해 나누어지는 영역이 몇 개인지는 오일러 공식 으로 구할 수 있습니다. 직선과 직선들이 만나는 교점들을 정점으로 보면, 전체는 평면 그래프가 됩니다. 따라서, $V - E + F = 2$ 에 따라, $V$ 와 $E$ 를 알고 있으므로 $F$를 구하면 됩니다.
각 영역마다 점이 하나씩 들어있는지 여부를 판정하는 것은 어려운 일이므로, $M$개의 점이 서로 다른 영역을 몇개 커버하는지로 문제를 바꾸어 풀 것입니다. 각 점 $x$에 대해, $f(x)$를 $N$ 차원의 boolean vector로, $f(x)_i$ 는 $x$가 $i$번째 직선의 오른쪽에 있는지 (CCW) 여부로 정의합니다. 이제, 각 ‘영역’은 $f(x)$ 가 서로 다른 점들의 집합이므로, $N$개의 점에 대해 각각 $f(x)$ 벡터를 구하고, 이들중 서로 다른 것이 몇 개인지를 판정하면 됩니다.
저는 $M$개의 점들을 집합으로 관리하고, 직선을 하나씩 추가하면서, 오른쪽과 왼쪽에 있는 점들이 갈린다면 새로운 집합으로 갈라주는 식으로 구현했습니다. 이렇게 하는 경우, 빈 집합을 만들지 않는다면 $M$개 이상의 파티션을 나누는 일은 없으므로 $O(N^2 + NM)$ 시간 알고리즘을 구현할 수 있습니다 ($N^2$는 교점을 모두 구해야 하므로)
기하 문제가 늘 그렇듯 교점을 주의해야 합니다. 다만, 이 문제의 경우 서로 다른 세 직선이 한 점에서 만나지 않는다는 것을 보장하므로, 직선들을 방향벡터에 따라 나누어 관리하면 쉽게 할 수 있습니다.
Note: 구현해보지는 않았지만, $f(x)$를 모두 구한 다음, 벡터들을 해싱하거나 정렬해서 비교하기만 한다면 훨씬 쉽게 구현할 수 있을 것입니다.
BOJ 3406 / ICPC Central European Regional Contest 2012 B
난이도: Diamond V
문제 요약: 0 또는 1의 값을 갖는 수열 $x_1 \dots x_N$이 다음 recurrence에 의해 시간에 따라 변화한다. ($\oplus$ 는 XOR) \(x_i(t) = x_{i-1}(t-1) \oplus x_{i+1}(t-1)\) (단, 맨 왼쪽과 오른쪽에 벽으로 막힌 값은 0으로 간주) 무한히 많은 시간이 지났을 때, 수열 전체가 $x_i = 0$이 되는가?
일종의 콘웨이 생명 게임 같은 문제입니다. 한참 고민하다가 풀이를 보고서야 정말 멋진 문제였음을 알게 되었습니다.
셋 총평: 문제가 이상하다고 생각했지만 항상 깊이 생각하다보면 이상한건 나라는 사실을 깨닫게 됩니다. 특히 전혀 생각지 못한 다익스트라의 아이디어를 쓰는 3번이나, 분할정복으로 깔끔하게 맞추는 5번은 풀이가 깔끔하고 아름다운데도 정말 어려웠습니다. :cry:
]]>Xiaowei Ye, Rong-Hua Li, Qiangqiang Dai, Hongzhi Chen, and Guoren Wang. 2022. Lightning Fast and Space Efficient k-clique Counting. In Proceedings of the ACM Web Conference 2022 (WWW ‘22)
This paper develops an efficient algorithm for $k$-clique estimation via uniform sampling. By employing a greedy coloring strategy, the algorithm initially reduces the sample space to the set of $k$-colored sets/paths, which are structures that have a high likelihood of being cliques. The counting of the number of $k$-colored sets/paths is achieved through dynamic programming.
For sparse graphs, PIVOTER (WSDM 20) already performs remarkably well. The authors thereby propose a framework where given graph is split into sparse and dense region, and run PIVOTER on sparse region, while the dense region is dealt with the sampling algorithm authors propose.
Overall, impressive results (accuracy and speed) with relatively simple algorithm. Implementation seems also reasonably doable. Giving more structure on the graph via proper coloring to reduce the sample space seems like a really nice idea.
]]>Kim, H., Choi, Y., Park, K., Lin, X., Hong, S. H., & Han, W. S. (2021). Versatile Equivalences: Speeding up Subgraph Query Processing and Subgraph Matching. Proceedings of the ACM SIGMOD International Conference on Management of Data, 925–937. https://doi.org/10.1145/3448016.3457265
이번에 정리할 논문은 제가 2020년 2학기에 연구실 학부생 인턴으로 지도 받았던 서울대학교 컴퓨터이론 및 응용 연구실 (박근수 교수님 연구팀) 에서 이번 2021년 SIGMOD에 발표한 논문이자, 제 이번 창의통합설계와 밀접한 관련이 있는 논문입니다. (그래서 다른 논문에 비해 훨씬 자세히 읽었습니다. 이 논문을 똑같이 구현할 각오(?) 를 하고 있었기 때문에…)
이 알고리즘도 크게는, Filtering - Matching order - Backtracking 의 틀 위에서 설명될 수 있습니다.
더 강한 필터링을 위해, 어떤 적당한 순서로 DP를 돌려서 Candidate Set (CS) 라는 자료구조를 구축합니다. 그래프에서 DP를 돌리기 위해서는 DP할 순서가 필요하기 때문에, Query Graph로부터 DAG를 만듭니다. 이때 DAG는 label이 좀 unique하면서 degree가 큰 정점을 하나 잡아서, 그 정점에서 BFS를 돌려서 얻을 것입니다. 이를 $q_D$라고 하고, 나중에 역방향으로 돌리기 위해 $q_D$의 inverse DAG $q_D^{-1}$ 로 만듭니다.
CS는 각 $u \in V_q$에 대해, 집합 $C(u)$ 와 그 집합의 $v_{ip} \in C(u_i)$, $v_{jq} \in C(u_j)$ 사이에 이어진 간선을 저장하는 자료구조입니다. 이 자료구조는 DAF[^ref-daf] 에서 제시된 자료구조이지만, VEQ에서는 더 개선된 버전으로 적용됩니다. 최초의 CS는 label과 $G$에서의 연결관계만 가지고 대충 만들어 놓고 (label이 같은 정점들을 집어넣고, 그 정점과 연결되어 있는 정점을 BFS 순서로 돌면서 빌드) 이를 줄여나갈 것입니다.
DAG DP는 기본적으로 $\abs{V_q}\abs{V_G}$ 크기의 큰 boolean DP 테이블을 채워나가는 방법입니다. 이 테이블 D는 $D(u_i, v_j)$ 가 곧 $v_j \in C(u_i)$를 의미하는 DP가 됩니다. DAG의 리프부터 시작해서 올라오면서, 다음을 계산합니다.
$D(u, v)$ 가 1일 필요충분조건 : $u$의 모든 자식 노드 $u_c$에 대해,
이 조건까지는 DAF에서 사용한 DP의 정의입니다. VEQ에서는 여기서 더 강한 조건을 요구하여 더 강한 필터링을 달성합니다.
$D(u, v)$ 가 1일 필요충분조건 : $u$의 모든 자식 노드 $u_c$에 대해 DAF에서의 조건을 만족하며, $(u, v)$가 모든 label $l$에 대해 다음을 만족한다.
즉, 직관적으로, 만약 $u_1$을 $v_1$에 매핑해놓고 보니까 $u_1$에는 라벨이 $l$인 이웃이 여섯개 있는데 $v_1$에서 extend가능한 라벨 $l$인 이웃이 네개밖에 없는 상황을 미리 판단해서 방지한다는 것입니다.
이 조건을 VEQ 논문에서는 “Neighbor-Safety”라고 정의했습니다. 이 조건은 미리 preprocessing을 빡세게 해서 잘 구현하면 원래의 DP와 같은 시간 복잡도 $O(\abs{E_q}\abs{E_G})$에 가능하다고 합니다.
또한, 이 DP를 최대한 잘 줄이기 위해, query DAG를 여러개 쓰면 더 좋은 결과를 얻을 수 있습니다. 실제로는 $q_D, q_D^{-1}, q_D$ 순서로 반복하면서 쓰면 되는데, 논문에 의하면 3번만 하면 더이상 큰 의미 없다고 합니다.
Adaptive Matching Order란, Matching order를 미리 정하지 않고, 백트래킹 하는 중에 다이나믹하게 정해 나가겠다는 의미입니다. 현재까지 찾은 partial embedding $M$에 대해, $M$에 이미 포함된 정점과 이웃한 정점들을 extendable하다고 정의하고, 이때 Candidate Set $C(u)$에 들어 있는 정점들 중 partial embedding $M$을 고려할 때 $u$와 매칭 가능한 정점들을 $C_M(u)$ 라고 정의하겠습니다. (즉, $C(u)$에 있더라도, 이미 $M$에서 이웃들을 매칭했을 때 $u_c$를 $v_c$에다가 대고 매치했다면, $v_c$와 이웃하는 점만 남기고 나머지는 날리겠다는 말입니다)
Extendable vertex중 하나를 택해서 다음 정점으로 삼고 backtracking해야 합니다. CFL-Match와 DAF의 경우 이부분에서 vertex를 core-forest-leaf 또는 core-leaf로 나눠서 매칭하는 전략을 쓰는데, 이 논문에서는 이와 같은 decomposition전략이 leaf가 적을 때 느려서 별로 좋지 않다고 주장합니다.
대신에, 다음과 같은 방법을 씁니다.
참고로, DAF의 경우 두개의 adaptive matching order 중 하나를 사용합니다. 이때 두 가지 중 하나가 $C_M(u)$의 크기가 작은 노드부터 매칭하는 것입니다.
백트래킹을 하는 중에도, 계속 Search space를 줄이고 싶습니다. 이를 위해서, Candidate Space를 잘 관찰하여 Equivalence Tree의 개념을 정립합니다.
$C(u)$의 원소 $v_i, v_j$에 대해, $v_i, v_j$와 연결된 $C(u_c)$의 vertex 가 모든 $u$의 이웃 $u_c$ 에 대해 같으면, 두 노드를 Neighbor equivalent in $C(u)$라고 정의합니다. 이를 통해, Cell 이라는 개념을 정의하는데, cell $\pi(u, v)$는 $C(u)$에서 $v$와 equivalent한 $v_i$들의 집합으로 정의합니다. 이미지는 VEQ 원본 논문의 이미지인데, 말로 설명하면 조금 귀찮지만 개념 자체는 직관적입니다.
Partial embedding $M$과, $C(u)$에서 equivalent한 노드 $v_i, v_j$가 있을때, $u \to v_i$를 매칭해 본 정보를 가지고 있다고 하겠습니다. 이때, $v_j$와 $v_i$는 그 특성이 매우 비슷한 노드이기 때문에, $u \to v_j$를 정말 모두 확인하는 것은 뭔가 기분이 매우 나쁩니다.
논문에서는 이를 위해, 마지막으로 subtree equivalence를 정의합니다. Partial embedding 을 탐색 트리처럼 쓰고 있다는 점에 주의해서 notation을 읽어야 합니다.
$M \cup (u, v_i)$를 루트로 하는 서브트리에서, $(u’, v_i)$는 더이상 나타나서는 안 되는 conflict들입니다 ($v_i$는 이미 $u$와 매칭되었으므로) 이들을 $I_M(u, v_i)$라 하고, 이 서브트리에서 $v_i \not\in \pi(u’, v’)$인 모든 매핑 (즉, $v_i$와 equivalence관계를 갖지 않는 매핑) 들의 집합을 $O_M(u, v_i)$ 라 합니다. 이때, Negative cell $\pi^{-}(u, v_i)$를 다음과 같이 정의합니다.
또한, $\delta_{M}(u, v_i)$를 다음과 같이 정의합니다. \(\bigcup_{(u', v') \in O_M(u, v_i)} \pi(u', v')\) 직관적으로 이는, $v_i$와 equivalent하지 않은 모든 매핑들에 대해서, 그 equivalence class들을 모은 것입니다.
이를 이용하여, Equivalence Set $\pi_M(u, v_i)$를 정의합니다.
이 equivalence class는, 진정한 subtree equivalence이기 때문에, $v_j \in \pi_M(u, v_i)$ 인 경우, $v_j$를 $v_i$대신 이용하는 모든 임베딩이 대칭적으로 존재합니다. 따라서, $u \to v_j$ 매칭을 아예 시도하지 않고 버릴 수 있습니다.
아래 그림도 VEQ 논문의 그림인데, 일부만 해석해 보도록 하겠습니다.
$\pi_M(u_2, v_3)$ 을 생각해 보겠습니다. (이 equivalence Set을 완전히 아는 것은 $u_2 \to v_3$ 쪽의 서브트리를 모두 돌고 난 뒤입니다) 트리를 관찰해 보면, $v_3$ 와 매칭되고 싶어해서 conflict를 내는 노드 $u_5$ 가 보입니다. 따라서, $I_M(u_2, v_3) = \Set{(u_5, v_3)}$ 입니다. Conflict가 발생한 적이 있으므로, $\pi^{-}(u_2, v_3)$ 을 계산할 때 $\pi(u_2, v_3)$ 와 $\pi(u_5, v_3)$의 intersection을 구합니다. 이는 figure 5를 보고 구하면 $\Set{v_4}$고, 서브트리에서 성공해본 적이 없으므로 $\pi_M(u_2, v_3) = \pi^{-}(u_2, v_3) = \Set{v_4}$ 입니다. 그러므로 $v_3 \equiv v_4$ 이고, $u_2 \to v_3$에서 성공해 본 적이 없으므로 $u_2 \to v_4$도 성공하는 임베딩이 없음을 알게 되어 탐색하지 않고 prune할수 있습니다.
이 $\pi_M(u, v)$가 subtree equivalence의 필요충분이라는 증명은 proceeding에 발표된 버전에서는 빠져 있는데, 이후에 증명하게 된다면 채워 넣겠습니다. 직관적으로는, 이후의 서브트리 매칭 시도에서 생기는 모든 conflict와 equivalence를 기억해서 반영하고 있기 때문에 증명은 technical하겠지만 그렇게 이상하지는 않은것 같습니다.
이 논문의 저자들은 VEQ 알고리즘을 기존의 SOTA 알고리즘들과 비교하여 성능을 측정하는 실험을 실행했고, 절대적인 소요 시간을 많이 줄일 수 있었습니다. 특히, 재미있는 결과 몇가지만 소개합니다. 비교 대상인 알고리즘들은 같은 큰 틀을 공유하는 CFL-Match, DAF, GQL, RI와 Constraint programming 기반의 Glasgow (시간만 비교) 입니다.
Koutra, D., Vogelstein, J. T., & Faloutsos, C. (2013). DeltaCon : A Principled Massive-Graph Similarity Function. Proceedings of the 2013 SIAM International Conference on Data Mining, 10(3), 162–170.
이번에 정리할 논문은 DELTACON 이라는 Graph similarity metric을 제시한, DELTACON: A Principled Massive-Graph Similarity Function으로, 2013년 SIAM International Conference on Data Mining에 발표된 논문입니다. (SIAM과 IEEE에서 진행하는 똑같은 이름의 Conference가 있어서, 이쪽을 보통 SDM으로 약칭합니다). 이 논문에서는 그래프 유사도가 만족해야 할 기본적인 기준들을 제시하고, 그 기준에 맞는 실제 유사도 메트릭을 제시하였습니다.
두 그래프 $G_1 = (V_1, E_1), G_2 = (V_2, E_2)$ 가 주어졌을 때, 우리는 두 그래프의 유사도
를 측정하는 어떤 좋은 메트릭을 갖고 싶습니다. 예를 들어, 큰 그래프 $G$가 시간의 흐름에 따라 변화한다면 - 즉, $G_1, \dots G_n$ 에 대해서, $d(G_i, G_{i-1})$ 이 최대인 시점 $i$를 확인하면 anomally를 알 수 있을 것입니다. 이 논문에서는 Node correspondence까지 주어진 상황에서의 그래프 유사도에 대해서만 다룹니다.
뭔가 노드 간의 연결관계를 수치화해서 알고 있다면, 이를 비교할 수 있을 것 같습니다. 예를 들어, Random walk with Restart 같은 방법을 이용 (이 방법에 대한 설명은 Advanced-algorithm 쪽으로 제가 포스팅한 적이 있습니다. 링크) 하여, 노드간의 연결정도를 행렬로 만들어 놨다면 이 행렬이 그래프의 어떤 구조를 가지고 있다고 볼 수 있습니다.
이 논문에서는, RwR은 아니고 이보다 좀더 최신이며 여러 좋은 성질을 갖는 Fast Belief Propagation이라는 방법을 사용합니다. 이 방법을 사용할 때, 노드 간의 연결성을 포함하는 $n \times n$ 행렬은 다음과 같이 계산됩니다. \(S = [s_{ij}] = \left(I + \epsilon^2 D - \epsilon A\right)^{-1}\) 여기서, $A$는 그래프의 인접행렬을, $D$는 노드 $i$의 degree를 $d_{ii}$로 하는 대각행렬을 사용합니다.
이 논문에서는 다음과 같은 몇가지 성질들을 Graph Similarity Measure가 가져야 할 성질들이라고 주장합니다. 상당수가 우리의 직관에 기반하였기 때문에 자연스럽게 이해할 수 있습니다.
DELTACON은 앞서 제시한 행렬 $S$를 이용, 다음과 같은 distance를 계산합니다. \(d(G_1, G_2) = \sqrt{\sum_{i = 1}^{n} \sum_{j = 1}^{n} \left(\sqrt{S_1(i, j)} - \sqrt{S_2(i, j)}\right)^2}\) 행렬간의 거리가 굉장히 특이하게 정의되는데, 이를 Jefries-Matusita distance 라고 부른다고 합니다. 루트의 차를 제곱하는 신기한 방식으로 동작하는데, 일반적인 Euclidean distance와는 달리 이 distance를 사용할 때 위 성질들 을 잘 만족합니다. 이 부분은 이 논문에서 수학적으로 엄밀하게 논증되지는 않았고, 데이터 그래프에 대해 실험적으로 검증되었습니다.
우리는 그래프 유사도를 $[0, 1]$ 에 집어넣고 싶기 때문에, $sim(G_1, G_2) = \frac{1}{1 + d(G_1, G_2)}$ 를 사용합니다.
이 sim 함수를 DELTACON이라고 부릅니다.
Anomally detection 등에 쓰인다는 것을 통해 알 수 있듯이, 그래프 유사도는 많은 수의 그래프를 대상으로 해야 할 수도 있기 때문에, 가능한 빨라야 합니다.
Deltacon 알고리즘에서 가장 오래 걸리는 부분은 $S$행렬의 계산입니다. $S$행렬은 $S = [s_{ij}] = \left(I + \epsilon^2 D - \epsilon A\right)^{-1}$ 와 같이 역행렬로 정의되는데, 일반적인 matrix가 아니라 특수한 graph structure가 있기 때문에 FaBP 알고리즘을 이용하여 $O(n^2)$ 시간에 계산할 수 있다고 합니다. 이후의 Matusita distance는 당연히 $O(n^2)$에 계산 가능하므로, 이 알고리즘은 $O(n^2)$ 입니다.
이 논문에서는 DELTACON의 좀더 빠르게 작동하는 approximation 버전을 제시하고 있습니다. $n^2$보다 빠르게 하기 위해서는, Matusita distance 계산이 일단 $O(n^2)$ 시간은 무조건 걸리기 때문에 행렬 자체를 줄여야 합니다. 이를 위해, affinity를 계산하기는 하는데 노드 $i$에 대해 모든 노드 $j$의 affinity가 아닌, 노드를 적당히 묶어 $g$개의 그룹으로 만들어서 $i$에서 $j$번 그룹으로의 affinity를 계산합니다. 이는 즉, $S$행렬에서 임의로 열들을 $g$개의 group으로 묶어서 더함으로써 $n \times g$행렬을 묶는다는 것입니다. $g$를 충분히 작게 하고, 구현을 잘 하면 이 알고리즘을 edge 개수에 대해 linear하게 돌게 할 수 있다고 합니다.
이때, 이렇게 approximate한 similarity는 항상 실제 similarity보다 큰 값을 갖습니다. (증명은 부록에 있고, 그렇게 어렵지는 않습니다. 링크)
DELTACON은 무엇보다 Graph Similarity Measure가 가져야 할 좋은 성질들을 (정성적으로나마) 제시하였고, 이 성질들을 만족하는 실제 similarity measure를 찾아냈으며, 실험적으로 이를 검증하였다는 의의가 있습니다. 특히 다른 Similarity measure (Graph edit distance, spectral methods 등) 들은 이러한 그래프에 대한 직관적인 성질들이 전혀 고려되지 않았는데, 이런 유사도들에 비해 DELTACON이 얼마나 제시한 조건들을 잘 맞추는지를 상당히 extensive한 실험을 통해 검증하였습니다. 특히 Graph edit distance 등 계산 시간이 굉장히 오래 걸리는 알고리즘들에 비해, exact도 quadratic이고 이를 edge 개수에 선형이 되게 더 개선했기 때문에 다양한 활용이 가능할 것 같습니다.
Graph의 노드 대신 edge에 label이 주어진다면, 이를 자연스럽게 확장할 수 있을까요?
Edge 하나가 변화하면서, 변화 전 그래프 $G$와 변화 후 그래프 $G’$간의 deltacon similarity 또는 그 근사값을 측정하는 더 빠른 방법은 없을까요? 바뀌는 edge가 1개인데 $n^2$ 이나 $n$ 시간을 지불하기는 좀 아깝습니다.
Zero Property에서, $d(K_n, G_n)$ 이 항상 0이 아니라 0으로 수렴한다는 것이 약간 오묘합니다. 전체적으로 그래프 유사도가 크기에 많은 영향을 받는 것 같습니다.
전자의 질문에 답하는 가장 보편적인 방법이 Pagerank, 후자의 질문에 답하는 보편적인 방법이 RWR입니다. 이번 포스팅에서는 이 두개를 같이 간단하게 알아보려고 합니다.
우리는 Directed graph를 기본 모델로 생각하겠습니다.
Pagerank는 Google이 검색 결과를 정리하기 위해 개발한 알고리즘으로, 웹페이지의 순위 (rank) 를 정하기 위해 고안되었습니다.
대략적인 motivation은 아래 두 가지입니다.
굉장히 직관적으로 말이 되는 원칙입니다.
기본적으로 Pagerank는 stochastic하게 매겨집니다. 즉, 어떤 노드 $i$의 pagerank값 $r_i$는 $i$뿐 아니라 스텝수 $j$ (시간이라고 받아들이면 됩니다) 의 영향을 받으며, $r_{i, j}$ 는 $r_{-, j-1}$ 들에 의해 계산된다는 뜻입니다.
지금까지의 논의를 선형대수학의 언어로 다시 써 보겠습니다.
Pagerank가 global한 노드의 중요도를 계산해 주는데 반해, Random Walk (with Restart) 는 Local한 관점에서의 중요도를 제공합니다. 어떤 노드 $u$에 대해, 각 노드 $i$ 의 중요도 벡터 $r_i = C_{u, i}$를 계산한다고 생각하면 되겠습니다. 기본적인 관점 (random-surfer) 이 Pagerank와 똑같기 때문에, Personalized Pagerank 라는 이름으로 불리기도 합니다. 1
노드 간의 어떤 연관성을 찾는 방법은 보통 두가지를 생각해 볼 수 있을 것입니다.
이 두 방법 모두, 명환한 한계가 있습니다. 예를 들어 SNS 그래프에서 나를 기준으로, A를 통해 B로, C를 통해 D로 갈 수 있다고 하겠습니다.
이때, A가 수많은 사람을 알고 있는 유명인이고, C가 일반적인 친구라면, 나는 B보다는 D와 더 가까운 사이라고 판단해야 합니다. 그러나 위 두 방법들은 이러한 차이를 잡아내지 못합니다. 이런 점에서 Random walk는 A에서 갈수있는 노드가 많다는 점을 Penalize하기 때문에 보다 적절하다고 할 수 있습니다.
위 Pagerank와 똑같지만, 한 노드 $u$에서만 시작하기로 합니다. Notation의 편의를 위해 Adjacent matrix를 $A$로, 이를 normalize해서 얻은 Weight matrix를 $W$ 로 쓰겠습니다.
RWR-vector는 다음 식을 통해 계산됩니다.
\(r_{i} = dWr_{i-1} + (1-d) e_u\)
여기서 $e_u$는 시작노드 $u$만 1인 standard basis vector입니다.
이 식이 벡터 $r$로 수렴한다고 하면, $r = dWr + (1-d) e_u$이므로, 이를 조금 정리하면 $(I - dW)r = (1-d) e_u$에서,
\(r = (1 - d) (I - dW)^{-1} e_u\)
이렇게 계산할 수 있습니다.
이 알고리즘은 실제로 쓰기에는 상당히 느리기 때문에 (행렬곱셈 연산이 느리므로…) 다양한 방법들이 개발되어 왔습니다. 특히, Pagerank는 한번 돌리면 모든 노드에 대한 정보를 얻으므로 그 cost가 amortize되지만, RWR은 쿼리노드가 바뀌면 처음부터 다시 해야한다는 점에서, 쿼리당 복잡도가 매우 높습니다. 이를 개선하기 위한 방법들에 대해서는 별도 포스팅으로 다룰 예정입니다.
공식적으로 발표된 논문에서는 아주 약간의 차이가 있으나, 식 정리의 문제이고 실제로는 identical합니다. ↩
Min Cut 문제란, 어떤 그래프 $G = (V, E)$ 가 주어졌을 때, $V$의 정점들을 두 집합 $S, T$ 로 나누어서, \(\Setcond{(u, v) \in E}{u \in S, v \in T}\) 즉, 한쪽 끝이 $S$에, 다른쪽 끝이 $T$에 들어가는 간선들의 개수를 최소화하는 문제입니다. Weighted graph에서는 간선의 개수가 아니라 weight의 합을 최소화하는 문제로 바꾸어 생각하면 됩니다.
우리는 논의를 위해, 편의상 그래프를 unweighted connected의 경우로만 한정하겠습니다. Directed / Undirected는 (그림은 undirected로 그리더라도) 사실 문제 자체가 똑같습니다. 알고리즘의 측면에서는 조금 차이가 있으므로, 좀더 일반적인 directed graph의 경우를 생각하겠습니다.
예를 들어 이 그림에서 빨간색 cut은 간선 3개짜리 cut이지만, 초록색 cut은 간선 1개짜리입니다.
Min cut 문제의 variation 중 하나는, s-t min cut 이라는 문제입니다. 이 문제는 $s, t$ 라는 두 정점이 각각 $S, T$에 속해야 한다는 추가 제약조건이 걸린 min cut 문제입니다.
이제, 일반적인 min cut을 풀고자 한다고 생각해 봅시다. 당연히, 모든 정점 페어를 $s, t$로 잡고 s-t min cut을 해보는 방법을 생각할 수 있으므로, 우리는 적어도 $O(V^5)$ 알고리즘을 가지고 있습니다. 이보다 나은 방법을 생각해 봅시다. 이하, 시간 복잡도를 쓸 때 정점이 $n$ 개, 간선이 $m$개라고 생각하겠습니다. 즉 $\abs{V} = n, \abs{E} = m$.
지금은 MIT의 교수로 계신 Prof. David R Karger가 제시한 Karger’s Algorithm은 Edge contraction이라는 연산에 기반하는, 매우 간단하고 elegant한 알고리즘입니다.
Edge contraction이란, 말 그대로 edge 양쪽 끝을 접합하는 연산입니다. Edge $e = (u, v)$를 contract한 그래프 $G / e$ 는 다음과 같은 과정을 통해 만들어집니다.
예를 들어 이런 식입니다. 그림을 보면 거의 바로 이해가 갈듯 합니다.
Karger’s Algorithm은 정말 어이가 없을 정도로 간단합니다.
min cut의 크기를 편의상 $K$ 라고 하고, 실제 cut edge의 집합을 $C$라고 하겠습니다. 이제, 위 알고리즘이 C를 반환할, 즉 올바른 답을 제공할 확률은 $K$개의 Edge가 $n-2$번의 contraction을 모두 살아남아야 합니다. 각 contraction에서는 남은 edge들 중 하나를 임의로 contraction해버리므로, 매 스텝을 모두 살아남을 확률은 \(\prod_{i = 0}^{n-3} \left(1 - \frac{K}{E - i}\right)\) 이렇게 계산됩니다. 그런데, $\frac{K}{E - i}$ 는 잘 생각해보면 좋은 바운드를 잡을 수 있습니다.
Contraction을 진행하는 과정 중 한 번이라도 만약 어떤 정점 $u$ 가 $d_u < K$ 를 만족한다면, $u$ 와 나머지를 자르는 cut의 크기가 $d_u$ 가 되기 때문에, 정의로부터 모든 정점의 degree는 $K$보다 언제나 크게 됩니다. 따라서 $i$번째 contraction 이전 남은 정점이 $n - i$개이므로 전체 edge의 개수는 $\frac{K(n-i)}{2}$ 개보다 크고, 위 확률 계산은 \(p_{success} \geq \prod_{i = 0}^{n-3} \left(1 - \frac{2}{n - i}\right) = \frac{1}{\binom{n}{2}}\) 이렇게 계산되게 됩니다.
편하게, 대충 성공 확률이 $1 / n^2$ 스케일이 된다고 하겠습니다 (이거보다 2배 좀 더되게 높습니다). 만약 우리가 이 알고리즘을 $n^2 \log n$ 번 시도한다면, 개별적인 성공확률이 $1 / n^2$ 인 베르누이 시행을 $n^2 \log n$ 번 하는 것이므로, 모두 실패할 확률은 $\left(1 - \frac{1}{n^2}\right)^{n^2 \log n}$ 이고, 이 값은 $1/n$ 미만입니다. 4
이정도 실패확률이라면 충분히 큰 $n$에 대해서 받아들일만 합니다. 따라서, 우리는 이 알고리즘을 $n^2 \log n$ 번 정도 실행하면 된다고 생각할 수 있습니다.
그래프 알고리즘이 대개 그렇듯 한번당 드는 시간은 구현하기 나름입니다. Adjacency matrix가 있다면 $O(n^2)$ 으로 구현하면 되고, Adj list가 있다면 $O(m)$ 비슷한 시간이 걸리는게 그럴듯해 보입니다. 가장 쉽게 짜는 방법은 Kruskal 알고리즘을 구현할 때처럼 구현하는 방법이고, 이 방법의 구현체는 (언젠가 제가 구현하면 구현체 링크를 올릴 예정입니다) $O(m \log m)$ 정도에 돌게 할 수 있습니다. 이렇게 짜면 $m \approx n^2$ 일 때 최대 $O(n^2 \log n)$ 이 되므로, 전체 복잡도는 $O(n^4 \log^2 n)$ 이 되겠습니다.
구현을 잘 하면 한번 iteration을 $O(m)$ 에 돌게 해서, $O(n^4 \log n)$ 에 구겨 넣을 수 있습니다만, 이건 그래프 구현을 잘 하는지의 문제이므로 우리는 다루지 않겠습니다. 다만, 알고리즘의 분석에는 중요하므로, Karger 알고리즘을 잘 구현했을 때의 복잡도는 한번 Iteration에 $O(n^2)$, $n^2 \log n$ 번 반복할 것이므로 $O(n^4 \log n)$ 이다 라고 쓰겠습니다.
Karger-Stein은 위 알고리즘과 거의 똑같지만, Clever idea가 살짝 추가되어 훨씬 빨라집니다. 다시 앞서의 확률 계산으로 돌아가겠습니다. \(p_{success} \geq \prod_{i = 0}^{n-3} \left(1 - \frac{2}{n - i}\right)\) 이제, 여기서 관찰하고 싶은 사실은, 초반보다 후반에 성공확률이 빠르게 낮아진다는 점입니다. 즉 초반에는 마구 뽑아도 대충 맞을것이라고 기대할 수 있지만, 후반에는 점점 불안해지기 시작한다는 것이죠. 따라서, 초반에는 대충 뽑아서 믿음을 가지고 돌리다가, 후반에는 좀 빡세게 보면 좋지 않을까요?
이점에 착안한 Karger-Stein은 노드 개수가 대충 $V / \sqrt{2}$개가 될때까지는 그냥 노드를 Karger처럼 줄이다가, 노드가 저만큼 남으면 두배로 많이 검토합니다. 이 수치를 쓰는 이유는, 계산해 보면 $V / \sqrt{2}$개의 노드가 남을 때까지 Contraction을 하면 이동안 min cut이 살아남을 확률이 $1/2$ 가 살짝 넘기 때문입니다. 즉, 원래 Karger 알고리즘은 $n$개부터 $2$개까지 줄여보는걸 한 스텝이라고 정의했지만, $n$ 개부터 $n / \sqrt{2}$ 개까지는 그냥 막 줄이고, $n / \sqrt{2}$ 부터 $n / 2$ 개가 될 때까지 줄여보는 행동은 두번 해서 나은걸 고르고… 이런 식입니다. 단, 재귀적으로 작동한다는 점을 주의해야 합니다. Pseudocode를 쓰면,
여기서 contract(G, t)
함수는 $G$의 edge가 $t$가 될 때까지 랜덤하게 contraction해서 줄이는 함수입니다. 6은 별 의미가 있는 상수는 아니고, 그냥 base case를 준 것으로 생각하시면 됩니다.
이 알고리즘의 성공 확률과 실행 시간에 대해 이해해 보겠습니다. 단, 위키피디아나 여러 자료에는 ceil 등으로 좀 정확하게 써있지만 우리는 어차피 big-O notation에 ceil을 하냐마냐는 영향도 없고… 대충 모든 수를 정수라고 생각하고 넘기겠습니다. $n / \sqrt{2}$ 같은걸 대충 쓰기로 합시다. (사실 분석에는 아무 문제 없습니다!)
편하게, Ceiling 같은거 다 날리고 점화식을 써 보겠습니다. 위 Pseudocode의 fastmincut(G)
가 정점 $n$개의 그래프일 때, Contract 한번이 정점 개수만큼의 시간을 소모함을 고려하면5 , $\sum_{i = n / \sqrt{2}}^{n} i$ 는 $O(n^2)$ 이므로 (대충 $n^2 / 4$ 정도 된다는걸 보이기 별로 어렵지 않습니다),
\(T(n) = 2 T(n / \sqrt{2}) + O(n^2)\)
이런 점화식을 얻습니다. 우리 모두 알고리즘 시간에 이미 배운 마스터 정리를 쓰면, $\log_{\sqrt{2}} 2 = 2$ 이므로, $T(n) = O(n^2 \log n)$ 을 얻습니다. 즉, 한번 연산에는 $n^2 \log n$ 시간이 걸린다는 것입니다. 앞서의 Karger과 비교하면, 두배로 연산을 늘리는 과정에서 $\log n$ 만큼의 시간을 추가로 지불했다는것을 알 수 있습니다.
이제 한번 시도의 성공 확률에 대해 알아야 합니다. $P(n)$ 을 $n$개 정점에 대해 fastmincut
의 결과가 올바를 확률이라고 하면, 이 함수가 실패하기 위해서는 두 개의 fastmincut(n / sqrt(2))
가 모두 실패해야 합니다. 따라서, 다음 점화식을 쓸 수 있습니다.
\(P(n) = 1 - \left(1 - \frac{1}{2} P\left(\frac{n}{\sqrt{2}}\right)\right)^2\)
1 - (둘 다 실패할 확률)
을 구하므로, 이는 다시 1 - (하나가 실패할 확률)^2
가 됩니다. 하나가 실패할 확률은 1 - (하나가 성공할 확률)
이므로, 위와 같이 구하는 것이 정당합니다.이제, 이 식을 푸는 방법은 Induction입니다.
위 식을 잘 전개하면, $P(n) = P(n / \sqrt{2}) - \frac{1}{4} P(n / \sqrt{2})^2$ 임을 알 수 있습니다.
우리는 이제 $P(n) \geq \frac{1}{\log{n}}$ 을 주장합니다. (단, 로그는 로그2) By induction, 다음의 오른쪽 부등호를 증명하면 증명이 끝납니다. \(P(n) \geq \frac{1}{\log(n/\sqrt{2})} - \frac{1}{4\log^2(n/\sqrt{2})} \geq \frac{1}{\log n}\) 이 부등식을 직접 풀기는 조금 귀찮지만, 별로 어렵지는 않습니다.
밑이 2인 로그를 쓰고 있으므로, 위 식은 \(\frac{1}{\log n - 1/2} - \frac{1}{4(\log n - 1/2)}\) 이고, 이를 통해 다음과 같은 식을 얻습니다. \(\frac{4 \log n - 3}{4\log^2 n - 4\log n + 1} = \frac{1}{\log n} + \frac{1 - 1 / \log{n}}{4\log^2 n - 4\log n + 1} \geq \frac{1}{\log n}\)
이렇게 얻습니다. 부등식을 더 열심히 맞추면 $2 / \log n$ 인가? 하는 바운드도 잡을 수 있을텐데, 별로 중요한 논의는 아닙니다.
어쨌든, 우리는 한번 성공확률이 $1 / \log n$ 수준임을 알았습니다.
몇번 수행할 것인지만 정하면 끝입니다. 앞서 Karger 알고리즘의 시간 복잡도 증명에서 했던 것과 똑같은 연산을 해 보면, 성공확률이 $p(n)$ 인 베르누이 시행을 반복해서 $1/n$ 이하의 실패확률을 갖게 하려면, $q(n)$ 번 실행한다고 할 때 \(\left(1 - p(n)\right)^{q(n)} \leq 1 / n\) 이 식을 목표로 하는 것인데, $(1 - x)^{1/x}$ 의 값이 $1/e$ 이하임을 다시 이용 ($p(n) \leq 1$이므로!), $q(n)$ 을 $\frac{\log n}{p(n)}$ 번으로 잡아주면 된다는 것을 알 수 있습니다. 따라서, $q(n) = \log^2 n$ 으로 잡아주면 됩니다.
우리는 개별 시간이 $O(n^2 \log n)$ 인 수행을 $\log^2 n$번 수행하기로 결정했으므로, 전체 시간복잡도는 $O(n^2 \log^3 n)$입니다. 간단한 아이디어로 충격적인 향상을 이루었음을 볼 수 있습니다.
특히 $n^2$ 비슷한 시간이 나왔다는게 중요한데, 간선이 $n^2$ 개일 때 적어도 이 간선들을 검토는 해봐야 하므로 이 문제는 이론상 $O(n^2)$ 보다 빠를 방법이 아예 없습니다. 어렵지 않은 아이디어를 잘 이용해서 이정도까지 복잡도를 내렸다는 점에서, Randomized algorithm의 힘을 잘 보여주는 예시가 아닌가 싶습니다.
이 알고리즘의 재밌는 extension은 $k$-cut입니다. $k$-cut이란, 노드를 $k$개의 connected component로 쪼개기 위한 min cut을 구하는 문제입니다. 우리가 지금까지 공부한 문제는 $k = 2$ 인 경우라고 생각하면 되겠습니다.
이 문제가 재미있는 이유는, 조금만 extension했을 뿐인데 미친듯이 어렵기 때문입니다. 이 문제는 $k$도 입력으로 주어지는 경우, NP-complete함이 잘 알려져 있습니다. 간단히 생각해보면, 2-cut은 적어도 s-t min cut 문제로 환원한다음 그걸 디닉으로 푸는 방법이 있었는데, 이 문제는 플로우 모델링이 아예 안 됩니다.
다양한 상황에서 approximation을 한다던가 하는 아이디어들이 연구되고 있지만, 쉽지 않습니다. Gomory-Hu tree를 쓴다던가 등등…
Karger-Stein algorithm은 $k$-cut에 대해 굉장히 잘 대응합니다. 단순히, 최종적으로 남기는 vertex를 2개가 아닌 $k$개로 남기면 됩니다. 이 방법이 성공할 확률이 그래서 얼마인지, 복잡도가 얼마인지 등은 굉장히 어려운 문제입니다.
Interestingly, Karger-Stein 알고리즘을 정말 잘 분석하면 $k$-cut에 대해 이 알고리즘이 optimal하다는 것을 보일 수 있다고 합니다. 이 글을 제가 쓰게 된 이유도 엊그제 이 주제 (Karger-Stein is optimal on $k$-cut) 를 다루는 세미나가 있었기 때문에 공부했던 내용을 리뷰할 겸 해서 쓰게 된 것인데요. 언젠가 저 논문을 전부 읽을 수 있을지는 사실 자신이 없습니다. 굉장히 재밌어 보이지만 증명이 Martingale을 쓰는 등 상당한 배경 지식을 요구하는 것 같아 보였습니다. 관심이 있으신 분들은 2019년 논문 링크 가 있습니다.
별론으로, 세미나에서는 Karger 알고리즘을 랜덤하게 edge들을 순서대로 고르는 대신, 각 edge들에 exponential clock이라고 해서, essentially 각 edge에게 특정 시간에 이벤트가 일어날 확률을 부여하고 그 이벤트가 터지면 contraction해 버리는 식으로 알고리즘을 살짝 다르게 분석했습니다. 이 method가 있다는 것을 다른 곳에서 들었었는데, 처음 들었을 때는 아 그렇구나 라고만 생각했는데 이런식으로 그래프 알고리즘에 적용하면 문제를 연속적인 공간으로 끌고와서 해석적인 기법들을 이용한 분석이 가능하다는 것을 새로 배웠습니다.
Stephen Boyd, Lieven Vandenberghe, Convex Optimization, Chapter 4 ↩
Sparse graph에 대해서는 Orlin 등 더 빠른 알고리즘들이 있지만, 우선은… ↩
Edge contraction을 정의할때 edge가 겹치면 하나만 남기는 저자들이 있는데, 저희는 그러지 않겠습니다. ↩
$\left( 1 - \frac{1}{x}\right)^x \leq e^{-x}$ 가 $1/e$ 보다 작음을 이용합니다. ↩
위 Karger 알고리즘의 시간 복잡도 분석에서 말한 바와 같이 Kruskal처럼 구현하면 여기에 로그가 하나 더 붙습니다만, 우리는 일단 구현을 잘해서 $O(n)$ 에 한 contraction을 처리할 수 있다고 하겠습니다! ↩