※ 이 공부기록은 부정확한 내용이 다수 포함되어있을 수 있다. 또한 이 글은 정보 전달이 주 목적이 아닌 개인적인 기록용이어서 체계적으로 내용이 정리되어 있지 않을 수 있다. 읽는이의 양해를 바란다.

 

들어가기 전에

 

Sum Over Subsets DP, 줄여서 SOS DP는 PS/CP에서는 꽤 유명한 주제라고 생각한다. 이름값이 있는 알고리즘이다보니 글쓴이도 예전에 SOS DP를 익히려고 시도한 적이 있었으나, 그 때에는 개념을 읽어도 이를 적용해 해결할 수 있는 관련 문제를 제대로 찾지 못해 나중으로 미뤘었다.

 

앞으로 계속 익힐 주제들과 비교했을 때 SOS DP는 더 미루기에는 너무 유명한 주제라는 생각이 들었다. 더 미루지 말고 이런 생각이 든 김에 SOS DP와 그 관련 주제를 공부해 보자.

 

Submask Enumeration

 

원소가 \(K\)개인 집합 \(S\)의 각 부분집합 \(A\)는 \(K\)개의 비트를 이용하여 각 원소와 대응되는 비트를 키고 끄는 것으로 모두 나타낼 수 있다. 만약 \(S\)의 모든 각 부분집합 \(m\)에 대하여 각 집합의 모든 부분집합 \(m'\)에 접근하는 알고리즘을 설계한다면 총 몇 개의 \(m'\)에 접근하게 될까?

 

단순하게 생각하면, 부분집합의 개수가 가장 많은 \(S\)의 부분집합 \(m\)은 \(S\)이고 \(S\)의 부분집합의 개수는 \(2^K\)개이므로 일단 \(O(4^K)\)가 될 것이라고 생각할 수 있다. 그러나 실제로는 이보다 더 적은 \(3^K\)개의 \(m'\)에 접근하게 된다. 

 

\(A\)의 원소 \(k\)개인 부분집합은 \(\binom{K}{k}\)개이며, 각 부분집합은 \(2^k\)개의 부분집합을 가진다. 따라서 \(A\)의 원소 \(k\)개인 부분집합에서는 총 \(\binom{K}{k}2^k\)개의 \(m'\)에 접근하게 된다.

이를 모든 \(k\)에 대해 합하면 이항정리(binomial theorem)에 의해 \(\sum_{k=0}^K \binom{K}{k}2^k=3^K\)가 된다.

 

\(3^{17}\)이 129140163이므로, 제한시간이 넉넉하게 주어지는 문제의 경우 이러한 접근법으로 해결할 수도 있다.

 

참고로, bitmask를 활용하여 표현된 어떤 집합을 나타내는 정수\(x\)가 있을 때, \(x\)의 모든 부분집합은 초기값이 \(x\)인 변수 \(x'\)에 대하여 \(x':=(x'-1)\&x\)를 반복하는 것으로 모두 접근할 수 있다.

 

Sum Over Subsets DP

 

집합의 부분집합에 대한 몇몇 DP 문제는 위와 같은 접근으로 해결할 수 있지만, 모든 문제를 위와 같이 해결할 수 있는 것은 아니다.

 

Sum Over Subsets DP는 원소가 \(K\)개인 집합 \(S\)의 모든 부분집합 \(m\)에 대하여 대하여 \(f(m)\)의 값이 알려져 있을 때 \(\sum_{m'\subseteq m} f(m')\)의 값들을 \(O(K2^K)\)에 계산하는 동적 계획법 알고리즘이다.

 

각 부분집합 \(m\)과 원소의 인덱스 \(k\)에 대하여 \(dp(m, k)\)를 \(m\)의 부분집합 \(m'\)중 \(k\)를 초과하는 인덱스의 원소의 포함 여부는 \(m\)과 상태가 동일한 집합에 대한 \(f(m')\)의 합으로 정의하자. 그리고 초기값으로 \(S\)의 각 부분집합 \(m\)에 대하여 \(dp(m, -1)\)을 \(f(m)\)이라 정의하자.

 

\(dp(m, k)\)의 합을 구성하는 부분집합들을 인덱스 \(k\)의 원소를 포함하는 부분집합과 그렇지 않은 부분집합으로 나누어 생각해보자. "인덱스 \(k\)의 원소를 포함하는 부분집합"은 \(k\) 이상의 인덱스를 갖는 원소의 포함 여부가 \(m\)와 상태가 동일하다. 따라서 이 집합들의 \(f\)값의 합은 \(dp(m, k-1)\)와 같다. 그리고 "그렇지 않은 부분집합"의 \(f\)값의 합은 \(m\)에서 인덱스 \(k\)의 원소를 제거한 집합 \(m^*\)에 대하여 인덱스가 \(k\) 이상의 인덱스를 갖는 원소의 포함 여부가 \(m^*\)와 동일한 부분집합 \(m'\)들에 대한 \(f(m')\)의 합과 같다.

 

위의 관계를 식으로 나타내면 다음과 같다.

\(S\)의 부분집합 \(m\)에 대하여, \(m\)이 인덱스 \(k\)의 원소를 포함할 경우 \(dp(m, k) = dp(m^*, k-1) + dp(m,k-1)\)

\(m\)이 인덱스 \(k\)의 원소를 포함하지 않을 경우 \(dp(m, k) = dp(m, k-1)\)

 

구하고자 하는 값은 \(dp(S,K-1)\)이므로, 이를 계산하려면 위 형태의 점화식을 \(O(K2^K)\)회 계산하면 된다. 위 점화식을 이루는 연산은 단순 덧셈이니 \(O(1)\)에 실행할 수 있다. 따라서 점화식을 이루는 각 집합에 대한 접근이 O(1)에 이뤄질 수 있으면 이 알고리즘의 시간복잡도는 \(O(K2^K)\)가 된다. 이는 비트마스킹을 이용하여 간단히 해낼 수 있다.

 

정확히 위의 문제를 해결하고자 하는 경우, \(k\)값에 대한 모든 \(dp\)값을 계산한다면 \(k-1\) 이하의 값은 더 이상 필요없어지므로, \(k\)에 대해 순차적으로 계산하면서 각 \(k\)에 대하여 집합들을 적절한 순서로 방문하는 것으로 \(O(2^K)\)의 메모리를 활용해 문제를 해결할 수도 있다.

 

Sum Over Supersets DP

 

Sum Over Subsets DP는 원소가 \(K\)개인 집합 \(S\)의 모든 부분집합 \(m\)에 대하여 \(f(m)\)의 값이 알려져 있을 때 \(\sum_{m\subseteq m'\subseteq S} f(m')\)의 값들을 \(O(K2^K)\)에 계산하는 동적 계획법 알고리즘이다.

 

이 문제는 앞에서 살펴본 Sum Over Subsets DP와 완전히 동일한 방식으로 해결할 수 있다. 단순히 각 부분집합들을 \(S\)에 대한 여집합에 대응시키면 문제가 Sum Over Subsets DP와 동일해짐을 확인하자.

 

OR Convolution

 

두 수열 \(a_0, a_1, \cdots, a_{2^K-1}\)과 \(b_0, b_1, \cdots, b_{2^K-1}\)에 대하여 \(c_m=\sum_{i|j=m}a_ib_j\)을 만족하는 수열 \(c_0, c_1, \cdots, c_{2^K-1}\)를 정의하자. 여기에서 \(|\)은 두 정수의 bitwise OR 연산을 의미한다. 이 때 수열 \(c\)를 수열 \(a\)와 \(b\)의 OR Convolution이라고 한다.

 

편의를 위해, 집합 \(m'\)에 대하여 \(a_{m'}\)을 \(a\)의 "\(m'\)에 대응되는 비트마스크"번째 수로 정의하자.

그리고 두 함수 \(A\)와 \(B\)를 \(A(m)=\sum_{m'\subseteq m} a_{m'}\), \(B(m)=\sum_{m'\subseteq m} b_{m'}\)으로 정의하자.

 

이 때, \(A(m)\cdot B(m)\)의 값은 \( \sum_{m'\subseteq m} a_{m'} \cdot \sum_{m'\subseteq m} b_{m'} \)이 된다.

그리고 위의 값은 \(C(m)=\sum_{m'\subseteq m} c_{m'}\)의 값과 같다는 것을 어렵지 않게 알 수 있다.

 

\(a_n\)과 \(b_n\)으로부터 \(A\)와 \(B\)를 얻는 것은 SOS DP로 어렵지 않게 해낼 수 있다. 그렇다면 \(C\)로부터 \(c_n\)을 얻는 것은 가능할까? \(c_n\)으로부터 \(C\)를 얻는 것은 SOS DP를 통해 어렵지 않게 할 수 있으므로, 이를 거꾸로 수행하면 \(C\)로부터 \(c_n\)을 얻을 수 있다!

 

AND Convolution

 

두 수열 \(a_0, a_1, \cdots, a_{2^K-1}\)과 \(b_0, b_1, \cdots, b_{2^K-1}\)에 대하여 \(c_m=\sum_{i\&j=m}a_ib_j\)을 만족하는 수열 \(c_0, c_1, \cdots, c_{2^K-1}\)를 정의하자. 여기에서 \(\&\)은 두 정수의 bitwise AND 연산을 의미한다. 이 때 수열 \(c\)를 수열 \(a\)와 \(b\)의 AND Convolution이라고 한다.

 

AND Convolution 역시 위의 OR Convolution과 동일한 원리로 수행할 수 있다.

 

Subset Convolution

 

\(K\)값이 크지 않다면, 몇몇 문제는 \(O(K^22^K)\) 꼴의 시간복잡도 풀이를 갖기도 한다. 그 예시로 Subset Convolution을 소개한다.

 

두 수열 \(a_0, a_1, \cdots, a_{2^K-1}\)과 \(b_0, b_1, \cdots, b_{2^K-1}\)에 대하여 \(c_m=\sum_{i|j=m, i\&j=0}a_ib_j\)을 만족하는 수열 \(c_0, c_1, \cdots, c_{2^K-1}\)를 정의하자. 여기에서 \(|\)은 두 정수의 bitwise OR 연산을, \(\&\)은 두 정수의 bitwise AND 연산을 의미한다. 이 때 수열 \(c\)를 수열 \(a\)와 \(b\)의 Subset Convolution이라고 한다.

 

Subset Convolution이 OR Convolution과 다른 점은 \(i\&j=0\) 조건이 하나 추가된 것이 전부임을 확인하자. 이 점을 이용하면 Subset Convolution을 \(O(K^22^K)\)에 계산할 수 있다.

 

\(A(m, k)\)와 \(B(m,k)\)를 1인 비트의 개수가 \(k\)개인 \(m\)으로 나타나는 항의 값만을 이용한(나머지 항은 0으로 취급) \(a\)와 \(b\)에 대한 SOS DP의 결과라 하자. 그리고 \(C(m, k)=\sum_{k'=0}^{k}A(m,k')\cdot B(m,k-k')\)과 같이 정의하자. 이렇게 \(C\)를 정의하면 \(k\)가 \(m\)의 1인 비트 개수와 정확히 같을 때 \(A\)와 \(B\)에서 겹치는 비트가 없어야지만 OR 값으로 \(m\)이 정확히 나오게 강제할 수 있으므로, 각 \(k\)에 대하여 SOS DP의 undo하는 과정을 거치면 각 \(m\)에 대하여 \(m\)의 1인 비트 개수 \(k\)에 대해 (undo된) 값을 확인하는 것으로 Subset Convolution의 값을 모두 얻을 수 있다.

 

여담

 

bitmasking 문제를 만난다면 먼저 최대한 문제를 단순한 형태로 정리해보고, 두 mask 사이의 subset 관계를 활용하기 좋은 형태일 때 이 글의 내용을 풀이법 후보로 생각해보는 것이 좋다. 한편, mask 사이의 포함관계로 문제를 정리할 수 있는 경우더라도, Held-Karp Algorithm(bitmask TSP)이나 각 bitmask를 정점으로 하는 (방향)그래프에서의 BFS 등 더 쉽고 직관적인 방법을 활용할 수 있는지도 생각해보는 것이 좋다. 쉬운 방법으로도 문제를 해결할 수 있다면, 특히 대회에서는 문제를 굳이 어려운 방법으로 해결할 필요는 없다.

 

글쓴이의 글에서 소개한 접근 방식은 아니지만(그림 없이 다차원 누적합을 묘사하기 귀찮아서 생략했다), SOS DP를 \(K\)차원의 누적 합과 차이배열로 설명하기도 한다. 이 또한 흥미로운 내용이므로 알아두면 좋다.

 

관련 문제

 

이 글에서 Submask Enumeration을 설명하기는 했으나, 굳이 SOS DP로 안 풀어도 되는 제한의 문제를 SOS DP로 해결하는 것을 막으려는 목적으로 설명을 한 것이다. 또한 이 글에서 Subset Convolution을 설명하기는 했으나, 어디까지나 \(O(K^22^K)\)의 문제풀이 예시로 보여주기 위해서 설명했을 뿐 그 활용은 이 글의 범위를 넘는다.

 

아래의 문제들은 SOS DP, AND Convolution, OR Convolution과 관련된 문제들이다. 몇몇 문제는 Generating Function(생성함수)의 개념이 익숙하지 않다면 어려울 수 있다.

 

[BOJ25563] AND, OR, XOR

자기 자신에 대한 AND Convolution과 OR Convolution으로 AND와 OR의 경우를 해결할 수 있다. XOR의 경우는 간단하므로 생략한다.

 

[BOJ13759] &

Sum Over Superset DP의 결과가 나타내는 것이 무엇인지를 잘 생각해보자.

 

[BOJ23949] Shifts

각각의 guard에 대하여, 각 날 하루를 일했을 때의 \(H\)값을 초기값으로 넣고 Sum Over Subsets DP를 돌리면 나오는 결과가 무엇인지 생각해보자.

 

[BOJ2803] 내가 어렸을 때 가지고 놀던 장난감

각 장난감 상자는 사용하지 않거나 사용하므로, 각각 장난감의 사용 여부로 Sum Over Subsets DP를 한다면 convolution을 이용하여 문제를 해결할 수 있을 것이다. 그러나 각 장난감에 대하여 Sum Over Subsets DP를 사용하는 것은 당연히 무리다. 이를 최적화할 수 있을까?

 

[BOJ33102] Counting Pairs

조건을 만족하려면 각 비트의 상태가 어때야 하는지를 잘 생각해보자(1)

 

[BOJ18719] Binomial

조건을 만족하려면 각 비트의 상태가 어때야 하는지를 잘 생각해보자(2)

 

[BOJ30966] 관심사

원소의 개수가 가장 많은 공통부분집합이 무엇일까?

 

[BOJ27574] Digits of Unity

SOS DP와 그 역과정의 의미를 잘 생각해보자.

 

[BOJ25390] Traveling Junkman Problem

문제 풀이의 부분문제로 SOS DP가 사용될 수도 있다.

 

[BOJ27841] Problem Setting

주어진 전체집합의 부분집합을 이용해 만들 수 있는 chain의 경우의 수를 구해보자.

 

참고 자료

 

https://codeforces.com/blog/entry/45223

https://usaco.guide/plat/dp-sos

https://qwerasdfzxcl.tistory.com/35

https://37zigen.com/subset-convolution/

https://www.acmicpc.net/blog/view/127

https://codeforces.com/blog/entry/92153

https://codeforces.com/blog/entry/115438

 

 

728x90

공부기록을 남기기 시작하며

처음으로 오프라인 대회(Good Bye, BOJ 2025!)를 나가고 느낀 점이 있다.

 

그동안 쉬운 문제를 빠르게 해결하는 실력을 갖추기 위해 쉬운 문제를 대량으로 풀어왔다. 그 결실이 있었는지, 글쓴이는 대회에서 상대적으로 쉬운 문제를 빠르게 해결할 수 있게 된 것을 느꼈다. 그러나 구현이 많이 필요하거나 고급 이론을 활용해야 하는 문제에서 글쓴이가 많이 약하다고 느꼈다.

 

하지만 쉬운 문제도 빠르게 못 풀던 예전과는 다르게, 이제는 단순히 쉬운 문제를 반복해서 푸는 것보다 다양한 이론, 알고리즘, 자료구조를 접하고, 구현 실력이 필요한 문제 또한 시도해 대회 환경에서 더 많은 문제를 푸는 것을 목표로 해도 될 것 같는 생각이 들었다.

 

이제 한동안 잘 모르거나 얕게만 알던 다양한 주제를 공부하고 그 내용을 기록으로 남기려고 한다.


※ 이 공부기록은 부정확한 내용이 다수 포함되어있을 수 있다. 또한 이 글은 정보 전달이 주 목적이 아닌 개인적인 기록용이어서 체계적으로 내용이 정리되어 있지 않을 수 있다. 읽는이의 양해를 바란다.

 

이번 글에서 살펴볼 주제는 Minimum Steiner Tree in Graph이다.

 

PS/CP에서 찾아볼 수 있는 Minimum Steiner Tree 문제는 크게 둘로 구분지어 생각할 수 있는 것 같다. 그 중 하나는 그래프에서 정의되어 있고, 다른 하나는 좌표평면 위의 점과 유클리드 거리에 대하여 정의되어 있다. 이 글에서는 전자의 문제를 다룬다.

 

Minimum Steiner Tree in Graph

먼저 Minimum Steiner Tree가 무엇인지 알아보자.

 

정점의 집합 \(V\)와 (음이 아닌) 가중치가 있는 간선의 집합 \(E\)로 구성된 연결그래프 \(G\)에 대하여, \(V\)의 (공집합이 아닌) 부분집합을 \(K\)라 하자. 이 때, Steiner Tree는 \(K\)의 모든 정점을 포함하는 \(G\)의 subtree를 말한다. 또한 Minimum Steiner Tree는 모든 Steiner Tree 중 간선의 가중치의 합이 가장 작은 Steiner Tree를 말한다. 또한 \(K\)에 속한 정점들을 Terminal이라고 부른다.

 

\(K\)의 크기가 1이면 Minimum Steiner Tree는 그 정점만을 포함한 가중치 0의 subtree가 됨은 자명하다. 또한 \(K\)의 크기가 2이면 두 정점을 잇는 최단경로의 형태가 Minimum Steiner Tree가 될 것이며, \(K\)의 크기가 3이면 각 정점에 대하여 세 정점으로부터의 최단거리의 합을 구했을 때 그 합이 가장 작은 정점을 중심으로 \(K\)의 세 정점을 최단거리로 이은 형태가 Minimum Steiner Tree가 될 것이다. 그러나 \(K\)의 크기가 4 이상이면 이와 같은 단순한 접근은 점점 어려워지게 된다.

 

이 문제는 Minimum Spanning Tree를 구하는 문제의 일반화로 생각할 수 있다. \(K=V\)인 경우 Minimum Spanning Tree와 정확히 같은 문제가 됨을 관찰하자. 그러나 이 문제는 NP-Hard 문제임이 잘 알려져있다. 따라서 P=NP가 아닌 이상에는 Minimum Steiner Tree 문제를 다항시간 내에 해결하기 어려울 것이다.

 

Minimum Steiner Tree를 구하기 위한 다양한 approximation algorithm이 존재하지만 이러한 알고리즘은 PS/CP에서 나오는 문제들과 잘 맞지 않는다. 일반적으로 PS/CP에서는 정확한 해를 구할 것을 요구하는 문제가 주로 나오기 때문이다. 따라서 이 글에서는 Exact Minimum Steiner Tree를 구하는 내용만을 다룬다.

 

Dreyfus-Wagner Algorithm

Minimum Steiner Tree를 구하는 알고리즘으로 Dreyfus-Wagner 알고리즘이 잘 알려져 있다. 이 알고리즘은 다음과 같은 DP로 진행된다.

 

\(K\)의 부분집합 \(K'\)과 \(V\)에 속하는 정점 \(v\)에 대하여 \(dp[K'][v]\)를 \(K'\)에 속한 모든 정점을 포함하면서 \(v\) 또한 포함하는 가장 가중치가 작은 \(G\)의 서브트리의 가중치로 정의하자. 즉, \(dp[K'][v]\)는 \(K'\cup\{v\}\)에 대한 Minimum Steiner Tree의 가중치를 나타낸다.

 

이 때, \(dp[K'][v]\)의 값은 다음과 같은 두 가지 방식으로 다른 상태로부터 계산할 수 있다.

 

(1) 이 상태를 나타내는 Minimum Steiner Tree에서 \(v\)의 degree가 2 이상인 경우:

\(K'\)을 두 집합 \(K_1\)과 \(K_2\)로 나누면 \(dp[K'][v]=dp[K_1][v]+dp[K_2][v]\)가 되게끔 할 수 있다.

(2) 이 상태를 나타내는 Minimum Steiner Tree에서 \(v\)의 degree가 1인 경우:

그 Minimum Steiner Tree에서 \(v\)와 연결된 정점을 \(u\)라 하고, 그 연결 간선의 가중치를 \(w\)라 하자. 이 때 \(dp[K'][v]=dp[K'][u]+w\)가 성립한다.

 

(1)의 경우, \(K'\)의 모든 부분집합에 대하여 문제를 먼저 해결해둔다면 계산을 쉽게 처리할 수 있다. 다만 이 값을 계산하는 시점에는 \(K'\)를 어떻게 쪼개야 최적인지에 대한 정보를 가지고 있지 않으므로, 가능한 모든 방법으로 \(K'\)를 쪼개봐야 한다는 점에 유의하자.

(2)의 경우, 이 과정을 degree가 1이 아닌 정점이 나올 때까지 반복하면 \(K'\)의 Minimum Steiner Tree를 얻게 됨을 관찰하자. 따라서, \(K'\)의 Minimum Steiner Tree의 값에서부터 relaxation을 반복하여 값을 갱신해나가는 것으로 (2)의 계산을 처리할 수 있다. 이해하기 어렵다면 Dijkstra 알고리즘의 relaxation 과정을 생각해보자.

 

기저 상태를 각 \(K\)의 원소 \(k\)에 대하여 \(dp[\{k\}][k]\)의 값을 0, 나머지 상태의 값을 전부 (가상의) 무한대로 설정하고 위와 같이 \(dp\)의 값을 계산해 문제를 해결하자.

 

relaxation 과정에 binary heap을 사용한다고 가정하면, Dreyfus-Wagner 알고리즘의 시간복잡도는 \(O(V3^K+(V+E)2^K\log V)\)와 같다. 여기서 \(V3^K\)는 DP에서의 (1)의 처리에 드는 시간복잡도이고, \((V+E)2^K\log V\)는 DP에서의 (2)의 처리에 드는 시간복잡도이다. (1)의  시간복잡도를 이해하기 어렵다면, bitmasking으로 \(K\)까지의 정수가 나타내는 각 집합에 대하여 각각의 부분집합의 개수의 합이 \(O(3^K)\)과 같다는 내용을 찾아보도록 하자. (참고: 공부기록07)

 

따라서 Dreyfus-Wagner 알고리즘은 \(V\)와 \(E\)가 어느 정도 크더라도 \(K\)가 충분히 작다면 PS/CP에서 충분히 제한 내에 사용할 수 있다.

 

Dreyfus-Wagner 알고리즘 구현 팁

bitmask를 활용하여 표현된 어떤 집합을 나타내는 정수\(x\)가 있을 때, \(x\)의 모든 부분집합은 초기값이 \(x\)인 변수 \(x'\)에 대하여 \(x':=(x'-1)\&x\)를 반복하는 것으로 모두 접근할 수 있다.

 

Dijkstra 알고리즘을 문제 제한에 따라 다양한 방식으로 최적화할 수 있듯이, 위 알고리즘의 중간 과정인 relaxation은 문제 상황에 따라 문제에 따라 다양하게 변형해 복잡도를 낮출 수 있다. 예를 들어 노드가 적지만 dense한 그래프의 경우 우선순위 큐를 사용하지 않고 \(O(N^2)\)으로 relaxation을 처리할 수 있다. 또한 가중치의 크기가 매우 제한적이라면 Dial 알고리즘처럼 버킷을 활용해 relaxation을 처리할 수 있다.

 

Rectilinear Minimum Steiner Tree

Rectilinear Minimum Steiner Tree 문제는 좌표평면 위에 \(K\)개의 점이 주어졌을 때 이 점들을 축과 평행한 선분만으로 모두 연결하기 위해 그어야 하는 선분의 길이의 최솟값을 구하는 문제이다.

 

주어진 \(K\)개의 점에 대하여 두 축과 각각 평행한 직선을 그려 얻게 되는 격자형태의 그래프를 Hanan Grid라고 부른다.

 

\(K\)개의 점에 대한 Rectilinear Minimum Steiner Tree는 이 \(K\)개의 정점으로 생성된 Hanan Grid 위에서 항상 찾을 수 있다. 따라서 \(K\)가 충분히 작을 경우 Dreyfus-Wagner 알고리즘으로 Rectilinear Minimum Steiner Tree 문제 또한 해결할 수 있다.

 

여담

Dijkstra 알고리즘처럼 relaxation을 진행하는 아이디어는 Dreyfus-Wagner 알고리즘에서만 사용되는 것이 아닌 다른 많은 문제에서도 종종 사용되는 트릭이니 잘 기억해두자.

 

관련 문제

[BOJ12752] 도시들

직관적인 문제이다.

 

[ABC364G] Last Major City

Dreyfus-Wagner 알고리즘의 각 DP값의 의미를 잘 생각해보자. 각 경우에 대하여 Dreyfus-Wagner 알고리즘을 써야만 이 문제를 해결할 수 있을까?

 

[ABC395G] Minimum Steiner Tree 2

위 문제의 확장판이다. 주어지는 그래프가 complete graph이므로 \(O(N^2)\) relaxation을 진행하는 것이 좋다.

 

[CF152E] Garden

이 문제는 Minimum Steiner Tree 문제와 비슷하게 생겼지만 조금 다르다.

바로 주어진 문제 상황을 그래프로 모델링했을 때, 최소화해야 하는 가중치가 간선이 아닌 정점에 있다는 점이다.

그러나 Dreyfus-Wagner 알고리즘을 조금만 변형하면 이에 대응되는 문제 또한 해결할 수 있으니 잘 생각해보자.

또한 이 문제에서는 최소 가중치를 구하는 데에서 그치지 않고 해를 역추적하여 구성해야 한다.

 

[BOJ34144] 크로스링크

처음과 마지막 행 및 열에 속한 정점이 적어도 하나씩은 속한 트리 중 정점의 가중치 총합이 가장 적은 경우를 찾는 문제로 볼 수 있다.

비트마다 하나의 정점에만 대응되던 Minimum Steiner Tree에서의 Terminal 대신 특정 행 또는 열에 속한 정점을 포함했는가에 대한 정보를 하나의 비트에 담고 multisource Dijkstra 알고리즘처럼 Dreyfus-Wagner 알고리즘을 진행하는 것으로 문제를 해결할 수 있다.

각 정점의 가중치가 0 이상 35 이하의 정수로 매우 제약이 강하므로 Dial 알고리즘처럼 버킷을 활용해 시간복잡도를 줄일 수 있다.

 

참고 자료

https://en.wikipedia.org/wiki/Steiner_tree_problem

https://en.wikipedia.org/wiki/Rectilinear_Steiner_tree

https://en.wikipedia.org/wiki/Hanan_grid

https://codeforces.com/blog/entry/91601

https://kopricky.github.io/code/Academic/steiner_tree.html

 

728x90

+ Recent posts