import sys
def input():
return sys.stdin.readline().rstrip()
def find_parents(X):
if X == make_set[X]:
return X
make_set[X] = find_parents(make_set[X])
return make_set[X]
def union(x,y):
X = find_parents(x)
Y = find_parents(y)
if X !=Y:
if ranks[X]< ranks[Y]:
X,Y = Y,X
make_set[Y] = X
if ranks[X] == ranks[Y]:
ranks[X] += 1
return True
return False
T = int(input())
for tc in range(1,T+1):
N = int(input())
make_set = [i for i in range(N)]
ranks = [1 for _ in range(N)]
for _ in range(int(input())):
x,y = map(int,input().split())
union(x,y)
print(f'Scenario {tc}:')
for _ in range(int(input())):
x,y = map(int,input().split())
if find_parents(x) != find_parents(y):
print(0)
else:
print(1)
if tc != T:
print()

전형적인 분리집합 문제였다.

 

 

들어오는 입력대로 union을 해주었고, 서로의 부모가 다를시에는 0 같을 시에는 1이 출력되게 했다.

import sys
def input():
return sys.stdin.readline().rstrip()
def find_parents(X):
if X == make_set[X]:
return X
make_set[X] = find_parents(make_set[X])
return make_set[X]
def union(x,y):
X = find_parents(x)
Y = find_parents(y)
if X !=Y:
if ranks[X]< ranks[Y]:
X,Y = Y,X
make_set[Y] = X
groups[X].extend(groups[Y])
if ranks[X] == ranks[Y]:
ranks[X] += 1
return True
return False
N,M = map(int,input().split())
edge_list = []
total_pay = 0
for _ in range(M):
x,y,pay = map(int,input().split())
if x>y:
x,y = y,x
edge_list.append((x,y,pay))
total_pay += pay
K = 10**9
past_pay = 0
answer = 0
make_set = [i for i in range(N+1)]
ranks = [1 for _ in range(N+1)]
groups = [[k] for k in range(N+1)]
edge_list.sort(key=lambda x : x[2])
while edge_list:
x,y,pay = edge_list.pop()
p_x,p_y = find_parents(x),find_parents(y)
if p_x!= p_y:
answer = answer + len(groups[p_x])*len(groups[p_y])*(total_pay-past_pay)
answer = answer%K
union(x,y)
past_pay += pay
print(answer)

 

구사과님의 분리집합 추천 문제에 있어서, 풀었던 문제였는데 푸는데 어려웠던 문제였다. 

 

이 문제는 분리집합만을 이요해 푸는 문제인데, 푸는 아이디어는 다음과 같다.

 

모든 간선이 연결이 끊어져 있다고 생각을 하고, 간선의 비용이 가장 많은 비용 순으로 연결을 해주는 것이다.

 

 

문제에 주어진 Cost(u,v)는 u,v가 연결이 안될때까지 최소비용의 간선을 제거해주는 것이다.

 

즉 역으로 생각하면  간선의 비용이 가장 많은 비용 순으로 연결을 해줄때, 처음 u,v가 같은 집합이 되면,

 

그 전까지 연결된 비용을 제외한 나머지 비용들이, 이 u,v의 Cost가 될 수 있다.

 

 

즉 전체 간선의 연결비용을 구한 상태에서 비용이 높은 순으로 연결을 해주면서 연결이 되었을때 까지의 누적 비용을

 

뺀 값이 해당 cost(u,v)인 것을 알 수 있게 된다.

 

그러면 여기서 고민사항은 다음과 같다.

 

서로 단독집합일때에는 위와 같이 한다고 할때, 만약에 서로 다른 집합에 이미 속해있는 상황에서는 어떻게 해야할지, 

 

일 것이다.

 

이 문제는 문제에서 cost(u,v)에서 u<v 이므로, 우리는 한가지 정보를 더 저장을 시켜줘야한다.

 

그것은 부모에 해당 집합에 속해있는 노드를 저장시켜놓는것이다.

 

그래서 우리는 각 부모들에 노드를 저장시켜주고, 그 노드의 수를 서로 곱해주면 두 집합에서 나올 수 있는

 

u,v의 집합의 개수와 동일 하다.

 

정리하면

 

(total_pay - past_pay) *len(group[parent_u]) *len(group[parent_v]) 로 나타낼수 있다.

 

 

위의 코드를 통해 AC를 받을 수 있었다. 이 문제의 아이디어를 떠오르는데 힘들었고, 구현을 하는데 어려움이 많았다.

 

아직 분리집합에 대해 잘 모르고, 응용하는법에 대해 잘 몰랐다는 것을 알게 되었다.

 

 

 

 

import sys
def input():
return sys.stdin.readline().rstrip()
def find_parents(X):
if X == make_set[X]:
return X
make_set[X] = find_parents(make_set[X])
return make_set[X]
def union(x,y):
X = find_parents(x)
Y = find_parents(y)
if X !=Y:
if ranks[X]< ranks[Y]:
X,Y = Y,X
make_set[Y] = X
groups[X] +=groups[Y]
if ranks[X] == ranks[Y]:
ranks[X] += 1
return True
return False
N,M = map(int,input().split())
edge_list = []
total_pay = 0
for _ in range(M):
x,y,pay = map(int,input().split())
if x>y:
x,y = y,x
edge_list.append((x,y,pay))
total_pay += pay
K = 10**9
past_pay = 0
answer = 0
make_set = [i for i in range(N+1)]
ranks = [1 for _ in range(N+1)]
groups = [ 1 for _ in range(N+1)]
edge_list.sort(key=lambda x : x[2])
while edge_list:
x,y,pay = edge_list.pop()
p_x,p_y = find_parents(x),find_parents(y)
if p_x!= p_y:
answer = answer + groups[p_x]*groups[p_y]*(total_pay-past_pay)
answer = answer%K
union(p_x,p_y)
past_pay += pay
print(answer)

 

 

이 코드는 노드를 전부 저장하는게 아닌, 노드의 개수를 저장해주는 방식으로 바꾼 코드이다.

'알고리즘 > 백준' 카테고리의 다른 글

[BOJ/백준] 15661 링크와 스타트  (0) 2021.06.29
[BOJ/백준] 10711 모래성  (0) 2021.06.29
[BOJ/백준] 1398 동전  (0) 2021.06.29
[BOJ/백준] 1045 도로  (0) 2021.06.29
[BOJ/백준] 2250 트리의 높이와 너비  (0) 2021.06.22
import heapq
import sys
input = sys.stdin.readline
def find_parents(X):
if make_set[X] ==X:
return X
else:
make_set[X] = find_parents(make_set[X])
return make_set[X]
def union(a,b):
X = find_parents(a)
Y = find_parents(b)
if X == Y:
return False
if rank[X]< rank[Y]:
X,Y = Y,X
make_set[Y] = X
if rank[X] == rank[Y]:
rank[X] += 1
return True
P,W = map(int,input().split())
c,v = map(int,input().split())
weight_list = [list(map(int,input().split())) for _ in range(W)]
make_set = [i for i in range(P)]
weight_list.sort(key=lambda x : x[2])
rank = [1 for _ in range(P)]
result = float('inf')
while find_parents(c) != find_parents(v):
node_a,node_b,pay = weight_list.pop()
if union(node_a,node_b):
result = pay
print(result)

  이 문제를 푸는 방식은 다음과 같다. 최대 길이를 구하고, 그때의 최소 너비를 구하면 되는 것이다.

 

그러므로 크루스칼 알고리즘대로 푸는 대신 가장 앞에서부터 pop을 하던것에서 가장 뒤에서부터 pop을 하면서

 

그 두 노드가 같은 집합이 아닐때 합쳐주면서 그때의 pay를 result에 저장해주면 되는 방식으로 풀면 된다.

 

 

import sys
import heapq
input = sys.stdin.readline
P,W = map(int,input().split())
start_city,end_city = map(int,input().split())
graph = [{} for i in range(P)]
for _ in range(W):
x,y,pay = map(int,input().split())
graph[x][y] = max(graph[x].get(y,0),pay)
graph[y][x] = max(graph[y].get(x,0),pay)
max_width_list = [0]*P
max_width_list[start_city] = float('inf')
node_list = []
heapq.heappush(node_list,(-float('inf'),start_city))
while node_list:
maximun_width,cur_node = heapq.heappop(node_list)
maximun_width = -maximun_width
for next_node in graph[cur_node]:
cur_maximun_width = min(graph[cur_node][next_node],maximun_width)
if cur_maximun_width > max_width_list[next_node]:
max_width_list[next_node] = cur_maximun_width
heapq.heappush(node_list,(-max_width_list[next_node],next_node))
print(max_width_list[end_city])

 

 

프림 알고리즘으로 푸는 방식은 거의 동일하다. 대신 cur_maximun_width가 다른데, 현재의 간선의 너비와 지금까지 최대 간선의 너비와 비교해서

 

그 중 더 큰 값을 비교에 쓴다.

 

이 점만 주의하고, 최소힙이 아니라 최대힙을 사용하면 해당 문제를 풀 수 있다.

import sys
input = sys.stdin.readline
G = int(input())
P = int(input())
result = 0
plane = [int(input()) for _ in range(P)]
parents_number = [i for i in range(G+1)]
visited = [False]*(G+1)
cur_idx = 0
planing = True
while cur_idx<P and planing:
cur_plane_number = plane[cur_idx]
check = [cur_plane_number]
while visited[cur_plane_number] and cur_plane_number>0:
cur_plane_number = parents_number[cur_plane_number]
check.append(cur_plane_number)
if cur_plane_number == 0:
break
else:
visited[cur_plane_number] = True
for num in check:
parents_number[num] = cur_plane_number-1
cur_idx += 1
print(cur_idx)

이 문제는 프로그래머스의 호텔 방 배정하기하고 동일한 문제라고 볼수 있다.

 

주어진 위치가 있고, 1~위치 사이에 비행기를 도킹할 수 없으면, 멈추면 되는것이다.

 

호텔 방 배정하기하고 동일하게, 자신의 다음 도킹 위치를 저장시켜놓으면 된다. 

 

그러면서 최악의 경우를 방지하기 위해, 그 다음 도킹위치를 새로 갱신해주는 작업을 해주면 된다.

 

import sys
input = sys.stdin.readline
def find_parent(idx):
if idx == make_set[idx]:
return idx
else:
make_set[idx] = find_parent(make_set[idx])
return make_set[idx]
G = int(input())
P = int(input())
make_set = [i for i in range(G+1)]
result = 0
for k in range(P):
plane_num = int(input())
gate_num = find_parent(plane_num)
if gate_num < 1:
break
else:
make_set[gate_num] = make_set[gate_num-1]
result += 1
print(result)

이거는 좀 더 함수로 만들어놓은 것이다.

 

위와 똑같은 로직이지만, find_parent 라는 재귀함수를 통해, 자동적으로 갱신이 되게 해주었다.

 

 

호텔 방 배정하기 문제를 수월하게 풀었으면 이 문제도 수월하게 풀 수 있을거라 생각한다.

'알고리즘 > 백준' 카테고리의 다른 글

[BOJ/백준] 2268 수들의 합  (0) 2021.05.27
[BOJ/백준] 1368 물대기  (0) 2021.05.23
[BOJ/백준] 11779 최소 비용 구하기 2  (0) 2021.05.19
[BOJ/백준] 2239 스도쿠  (0) 2021.05.19
[BOJ/백준] 1874 스택 수열  (0) 2021.05.19
def find_parent(ind):
if make_set[ind] == ind:
return ind
else:
make_set[ind] = find_parent(make_set[ind])
return make_set[ind]
def union(x,y):
X = find_parent(x)
Y = find_parent(y)
if X == Y:
return 0
else:
if child_cnt[X]< child_cnt[Y]:
make_set[X] = Y
return_value = child_cnt[X] * child_cnt[Y]
child_cnt[Y] += child_cnt[X]
else:
make_set[Y] = X
return_value = child_cnt[X] * child_cnt[Y]
child_cnt[X] += child_cnt[Y]
return return_value
N,M,Q = map(int,input().split())
graph = [{} for i in range(N+1)]
make_set = [i for i in range(N+1)]
child_cnt = [1 for i in range(N+1)]
connect_input = [[],]
for _ in range(M):
x,y = map(int,input().split())
graph[x][y] = 1
graph[y][x] = 1
connect_input.append((x,y))
result = 0
disconnet_list = []
for _ in range(Q):
ind = int(input())
x,y = connect_input[ind]
graph[x][y] = 0
graph[y][x] = 0
disconnet_list.append(ind)
for i in range(1,M+1):
if i not in disconnet_list:
x,y = connect_input[i]
union(x,y)
while disconnet_list:
ind = disconnet_list.pop()
x,y = connect_input[ind]
result += union(x,y)
print(result)

 

이 문제는 역으로 생각하는 편이 제대로 푸는 방식이었다 윗 코드는 첫 풀이이다.

 

문제를 푸는 방식은 다음과 같다. 문제에서 주어진 끊어진 조건들을 전부 끊어졌다고 가정을 하고

 

끝에서부터 하나씩 연결을 해가면서 반대로 추적을 하는것이다.

 

그러므로, 먼저 전체 연결리스트를 들어온 순서에 맞게 index에 알맞게 저장을 해준다.

 

그리고 난뒤에 끊어진 목록들을 따로 저장을 해두고, 끊어지지 않은 연결들끼리 서로 union find를 해준다.

 

 

union_find를 하면서, 각자의 노드 개수를 저장해주는 리스트를 따로 만들어두고,

 

서로 다른 집합이 합쳐질때 그 두 개의 노드의 개수를 합쳐주는 로직을 해주면 된다.

 

이렇게 연결이 된 간선들로 union find를 1차적으로 진행을 한다.

 

 

그리고 끝에서부터 끊어진 간선들끼리 연결을 해준다. 그러면 그 간선을 끊기전 모습으로 돌아갈 수 있다.

 

이렇게 하면서 서로의 집합이 같으면 0을 반환하고, 그게 아니면 서로의 집합의 개수를 곱한 수를 반환하도록 했다.

 

import sys
input = sys.stdin.readline
def find_parent(ind):
if make_set[ind] == ind:
return ind
else:
make_set[ind] = find_parent(make_set[ind])
return make_set[ind]
def union(x,y):
X = find_parent(x)
Y = find_parent(y)
if X == Y:
return 0
else:
if rank[X] < rank[Y]:
X,Y = Y,X
size_a,size_b = rank[X],rank[Y]
rank[X] += rank[Y]
make_set[Y] = X
return size_a*size_b
N,M,Q = map(int,input().split())
make_set = [i for i in range(N+1)]
rank = [1 for _ in range(N+1)]
connect_input = [[],]
check = [True]*(M+1)
for _ in range(M):
x,y = map(int,input().split())
connect_input.append((x,y))
result = 0
disconnet_list = []
for _ in range(Q):
ind = int(input())
disconnet_list.append(ind)
check[ind] = False
for i in range(1,M+1):
if check[i]:
x,y = connect_input[i]
union(x,y)
while disconnet_list:
ind = disconnet_list.pop()
x,y = connect_input[ind]
result += union(x,y)
print(result)

 

좀 더 깔끔하고 빠른 풀이 방식이다. 첫 풀이 코드같은경우엔 느렸는데

 

그 이유는 간선이 연결됬는지 안됬는지를 구분하는데 not in 으로 했기 때문에, O(N)의 시간이 걸려서 느려진 문제가 있었다.

 

 

그래서 간선들이 연결이 됬는지 안됬는지를 구분하는 리스트를 만들어두고 바로 확인이 가능하도록 했다.

 

 

https://www.secmem.org/blog/2021/03/21/Disjoint-Set-Union-find/

 

Disjoint Set & Union-find

Disjoint Set Disjoint Set(서로소 집합, 분리 집합)이란 서로 공통된 원소를 가지고 있지 않은 두 개 이상의 집합을 말합니다. 모든 집합들 사이에 공통된 원소가 존재하지 않는다는 것을, 각 원소들은

www.secmem.org

 

설명은 잘 못하므로 위의 링크에 있는 Union_find를 보면 알겠지만, rank compression을 활용해서 시간을 좀 더 줄였다.

 

 

Union find는 크루스칼알고리즘에서 처음으로 알게 되었는데, 크루스칼에서만 쓰이는줄 알았는데,

 

생각외로 단독으로 쓰이는 곳이 많았다. 이걸 짜는데 어색해서 크루스칼 알고리즘을 잘 안쓰는데,

 

좀 더 숙달되도록 노력해야겠다.

'알고리즘 > 백준' 카테고리의 다른 글

[BOJ/백준] 2239 스도쿠  (0) 2021.05.19
[BOJ/백준] 1874 스택 수열  (0) 2021.05.19
[BOJ/백준] 1103 게임  (0) 2021.05.18
[BOJ/백준] 5875 오타  (0) 2021.05.17
[BOJ/백준] 9944 NxM 보드 완주하기  (0) 2021.05.17

1

import sys
input = sys.stdin.readline
def union(A,B):
X = find_parent(A)
Y = find_parent(B)
if X != Y:
if rank[X] < rank[Y]:
X,Y = Y,X
make_set[Y] = X
if rank[X] == rank[Y]:
rank[X] += 1
def find_parent(ind):
if make_set[ind] == ind:
return ind
else:
make_set[ind] = find_parent(make_set[ind])
return make_set[ind]
N,M = map(int,input().split())
make_set = [i for i in range(N+1)]
rank = [1 for _ in range(N+1)]
for _ in range(M):
command, A,B = map(int,input().split())
if command:
if find_parent(A) == find_parent(B):
sys.stdout.write('YES\n')
else:
sys.stdout.write('NO\n')
else:
union(A,B)
import sys
input = sys.stdin.readline
N = int(input())
M = int(input())
S = [0]*N
E = [0]*N
for _ in range(M):
x, y = map(lambda x : x-1 ,list(map(int,input().split())))
S[x] += 1
E[y] += -1
ind = 0
result = 0
big_cnt = 0
while ind<N:
if big_cnt:
big_cnt += S[ind] + E[ind]
if big_cnt == 0:
result += 1
ind += 1
else:
if S[ind] == 0:
result += 1
else:
big_cnt += S[ind]
ind += 1
print(result)

+ Recent posts