문제풀이/백준

백준 1647 도시 분할 계획 문제풀이

soo-dal 2024. 1. 24. 22:17

문제 

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

 

 

 

문제 접근 방식

두 집 사이에 항상 길이 필요하며 그 비용을 최소화 해야하기 때문에 최소 스패닝 트리를 활용해서 문제를 풀려고 했다. 이때 어떻게 최소 스패닝 트리를 활용해야하나 고민을 했는데 사고의 흐름을 정리하려한다.

 

1. 첫번째 사고

처음에는 최소 스패닝 트리를 구한 후 그 중 최대 간선의 길이를 찾아서 제거하면 되지 않을까 생각을 했다. 

하지만, 최소 스패닝 트리가 아닌 일반 스패닝 중 간선 하나를 제거해서 답안이 나오는 경우도 있을 수도 있다고 판단했다. 즉, 최소 스패닝 트리에서 간선을 제거하는 것이 정답을 보장할 수 없다고 판단하여서 다른 방법을 찾아봤다.

결과적으로는, 이렇게 풀어도 정답이 나오지만 다른 스패닝 트리에서 정답이 나오는 경우의 변수를 제거하지는 못하는 것 같다.

 

2. 두번째 사고

어떻게 풀어야할지 몰라 직접 입력 예시를 활용했다. 입력 예시를 크루스칼 알고리즘을 활용해서 간선을 하나씩 이어보다가 크루스칼 알고리즘이 계속해서 최소 간선을 선택하는 그리디라는 것과 간선을 하나 덜 선택하면 그룹이 두 개로 된다는 점을 알게 되어 이를 활용했다.

 

기존 크루스칼 알고리즘이 노드 개수 V보다 하나 더 적은 수의 간선을 이었다면, 여기서 (V-2) 개까지의 간선을 선택하여 완전한 최소 스패닝 트리가 되기 전까지만 노드를 이어준다.

 

 

시간 복잡도

유니온 파인드의 find, union 이 상수시간이 걸리고, 우선순위 큐의 시간복잡도가 O(ElogE)이므로 전체 시간복잡도는 O(ElogE)이다. E의 최댓값이 1e6이므로 시간내에 동작함을 알 수 있다.

 

 

코드

import sys
import heapq

input=sys.stdin.readline
class UnionFind:
    def __init__(self,n):
        self.parents=[i for i in range(n+1)]
        self.heights=[0 for _ in range(n+1)]

    def find_parent(self,node):
        if self.parents[node]==node:
            return node
        self.parents[node]=self.find_parent(self.parents[node])
        return self.parents[node]

    def union(self,u,v):
        u=self.find_parent(u)
        v=self.find_parent(v)
        if u==v:
            return

        if self.heights[u]<self.heights[v]:
            u,v=v,u

        self.parents[v]=u

        if self.heights[u]==self.heights[v]:
            self.heights[u]+=1

if __name__=='__main__':
    n,m=map(int,input().split())
    pq=[]

    for _ in range(m):
        a,b,c=map(int,input().split())

        heapq.heappush(pq,(c,a,b))

    union_find=UnionFind(n)
    count=0
    answer=0
    while pq:
        if count == n - 2:
            break
        c,a,b=heapq.heappop(pq)
        if union_find.find_parent(a)==union_find.find_parent(b):
            continue

        union_find.union(a,b)
        answer+=c
        count+=1

    print(answer)

 

 

회고

처음 답안을 제출할 때 틀렸습니다가 떴는데, 처음에는 v-2개의 간선을 선택하는 풀이 방향이 잘못된건가를 생각했다. 하지만 대부분의 경우에서 동작을 하는 로직이었기 때문에 특수한 상황에서 에러가 발생한다고 생각했다. 주어진 상황에서 N=2 이고 간선이 주어질 때가 가장 특수한 상황이었기에 해당 부분에서 오류가 발생하는 부분을 수정하였다. 

 

첫번째 코드

import sys
import heapq

input=sys.stdin.readline
class UnionFind:
    def __init__(self,n):
        self.parents=[i for i in range(n+1)]
        self.heights=[0 for _ in range(n+1)]

    def find_parent(self,node):
        if self.parents[node]==node:
            return node
        self.parents[node]=self.find_parent(self.parents[node])
        return self.parents[node]

    def union(self,u,v):
        u=self.find_parent(u)
        v=self.find_parent(v)
        if u==v:
            return

        if self.heights[u]<self.heights[v]:
            u,v=v,u

        self.parents[v]=u

        if self.heights[u]==self.heights[v]:
            self.heights[u]+=1

if __name__=='__main__':
    n,m=map(int,input().split())
    pq=[]

    for _ in range(m):
        a,b,c=map(int,input().split())

        heapq.heappush(pq,(c,a,b))

    union_find=UnionFind(n)
    count=0
    answer=0
    while pq:
        c,a,b=heapq.heappop(pq)
        if union_find.find_parent(a)==union_find.find_parent(b):
            continue

        union_find.union(a,b)
        answer+=c
        count+=1
        
        if count == n - 2:
            break

    print(answer)

 

 

추가

이번 문제에서는 2개의 그룹으로 분할하여 v-2개를 선택했지만, 추후에 변형으로 3개, 4개로 그룹을 쪼개는 방식을 구할 때도  크루스칼을 활용하여 v-3, v-4,...개의 간선을 선택하여 문제를 풀면 될 것 같다.