1. 문제
다음과 같은 문제를 생각해보자.
크기가 1인 집합이 N개 있고 각 집합은 1부터 N까지 각각을 원소로 갖는 집합이다.
{1}, {2}, {3}, ... , {N}
이 집합을 합치는 연산을 반복하여 최종적으로 {1,2,3,...,N}이 되게 하고 싶다.
생각할 수 있는 방법은 {1}을 {2}에 넣어서 {1,2}로 만들고, {1,2}를 {3}에 넣어서 {1,2,3}으로 만들고..
{1,2,3}을 {4}에 넣어서 {1,2,3,4},...를 반복하면 {1,2,3,4,..,N}이 된다.
결국 i번 집합을 i+1번 집합에 넣는 과정을 N-1번 반복하면 되는 것이다.
--------------------------------------------------------------------------------------------------------------------------------------------------
그런데, 관점을 바꿔서 생각해보자.
위 과정은 {1,2}를 {3}에 넣어서 {1,2,3}으로 만들었지만...
사실 컴퓨터 프로그래밍 관점에서 생각해본다면... {1,2}를 {3}에 넣을 때는
{1,2}를 하나씩 순회해서 {3} >> {3,1} >>> {3,1,2}로 2번의 연산을 거치는데
반대로 {3}을 {1,2}에 넣는다면 어떨까?
그러면 {1,2,3}으로 단 1번의 연산으로 {1,2,3}이라는 같은 결과를 얻는다.
더욱 극단적인 예시로 {1,2,3,4,...,10000000}을 {10000001}이라는 집합과 합친다고 한다면...?
{1,2,3,4,...,10000000}을 {10000001}에 넣을려면 10000000번의 연산을 해야 {1,2,3,4,...,10000001}이 될 것이지만
{10000001}을 {1,2,3,4,...,10000000}에 넣으면 1번의 연산으로 {1,2,3,4,...,10000001}이 된다.
생각해보면 너무나도 당연하다.
두 집합을 합쳐서 하나의 집합으로 만들때, 컴퓨터 프로그래밍 관점에서 생각한다면 원소의 개수가 작은 집합에서
원소의 개수가 많은 집합으로 합치는 것이 무조건 유리하다.
이러한 테크닉을 작은 집합에서 큰 집합으로 합치는 테크닉(smaller to larger)이라고 부른다
2. 증명
크기가 p인 집합 A와 크기가 q인 집합 B가 있다고 할 때, p >= q라고 하자.
A와 B가 합쳐진다면 크기가 작은 집합에서 큰 집합으로 합친다고 할때, B의 원소를 A로 옮기게 된다.
그러면 p >= q에서 양변에 q를 더해주면 p+q >= 2q가 된다.
즉, 작은 집합에서 큰 집합으로 원소를 옮길 때마다 이동한 원소가 속하는 집합의 크기가 최소 2배이상 증가한다.
전체 원소의 수가 N개로 제한되어 있는데 2배 이상 증가하는 집합이 생기므로,
모든 원소는 최대 logN번 움직일 수 있고, 전체 시간복잡도는 O(NlogN)이 된다.
3을 기준으로 볼때, 최악의 경우 log8 = 3번 움직이는 경우는...
{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}
{1,2} {3} {4} {5} {6} {7} {8}
{1,2} {3,4} {5} {6} {7} {8} (1번)
{1,2} {3,4} {5} {6,7} {8}
{1,2} {3,4,6,7} {5} {8} (2번)
{1,2,5,8} {3,4,6,7}
{1,2,3,4,5,6,7,8} (3번)
3. 연습문제
F - Colored Ball
AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.
atcoder.jp
4. 풀이
N개의 박스가 있는데 i번 박스에는 색 Ci인 공이 1개씩 들어있다.
q개의 쿼리를 처리하는데 박스 a의 모든 공을 b로 옮겼을때, 박스 b에서 서로 다른 색의 개수는 몇개인가
union find로 해야하냐... segment tree로 해야하냐.. F번이니까 어렵겠지 생각했는데..
의외로 생각보다 간단한 문제다
그냥 시키는대로 시뮬레이션 하면 된다
각 박스마다 서로 다른 색의 공을 가지고 있다는 것을 관리해야하니까 그것을 알 수 있도록 dictionary나 set으로 관리해줘야한다.
애초에 특정한 색의 공의 개수가 중요한건 아니고 각 박스가 서로 다른 색을 몇가지 가지고 있냐가 중요해서..
set으로 해도 될것 같기는 한디
나는 일단 dictionary로 해서 각 박스마다 초기에 어떤 색의 공을 가지는지 관리해두고..
매 쿼리마다 진짜 시키는대로 a에서 b로 옮기고 a를 빈 상자로 초기화한 다음... key의 길이를 print했는데
from sys import stdin
n,q = map(int,stdin.readline().split())
A = list(map(int,stdin.readline().split()))
box = {}
for i in range(1,n+1):
box[i] = {A[i-1]:1}
for _ in range(q):
a,b = map(int,stdin.readline().split())
for key,value in box[a].items():
if box[b].get(key,0) == 0:
box[b][key] = value
else:
box[b][key] += value
box[a] = {}
print(len(box[b].keys()))
시간 초과 나더라고...
작은 개수를 가진 박스에서 다른 박스로 옮기는데는 그냥 옮겨도 상관 없지만...
10000개의 공을 가진 박스에서 3개의 공을 가진 다른 박스로 옮길때... 10000개나 옮겨야하잖아
억지로 만들면 색깔이 20만개 가능하니... 최악의 경우 20만*20만으로 당연히 시간초과날듯
애초에 이 생각 하긴 했는데.. 어떻게 해야하는지 몰랐지
근데 여기서 바로 "작은 집합에서 큰 집합으로 합치는 테크닉"이 필요하다 이거지
박스 a와 박스 b의 크기를 비교해서 a에서 b로 옮기는거지만 a의 크기가 b보다 크다면...
b에서 a로 옮기고 a라는 이름을 b로만 바꿔주면 끝이다.
두줄만 추가하면 0.5초정도 걸리더라
from sys import stdin
n,q = map(int,stdin.readline().split())
A = list(map(int,stdin.readline().split()))
box = {}
for i in range(1,n+1):
box[i] = {A[i-1]:1}
for _ in range(q):
a,b = map(int,stdin.readline().split())
#작은 집합에서 큰 집합으로 합치는 테크닉
#a에서 b로 옮기고 a를 빈 상자로 만들지만..
#반대로 생각하면 b에서 a로 옮긴 다음 a라는 이름을 b로 바꿔주면,
#a에서 b로 옮기는거나 결과는 마찬가지다.
if len(box[a].items()) > len(box[b].items()):
box[a],box[b] = box[b],box[a]
for key,value in box[a].items():
if box[b].get(key,0) == 0:
box[b][key] = value
else:
box[b][key] += value
box[a] = {}
print(len(box[b].keys()))
각 공의 개수는 상관없으니까 set으로 관리해도 상관없을것 같아
0.4초 정도로 0.5초보다 조금 더 빠르긴함
from sys import stdin
n,q = map(int,stdin.readline().split())
A = list(map(int,stdin.readline().split()))
box = [0]*(n+1)
for i in range(1,n+1):
box[i] = set([A[i-1]])
for _ in range(q):
a,b = map(int,stdin.readline().split())
if len(box[a]) > len(box[b]):
box[a],box[b] = box[b],box[a]
for c in box[a]:
box[b].add(c)
box[a] = set()
print(len(box[b]))
'경쟁 프로그래밍 > Atcoder' 카테고리의 다른 글
ABC 333 D번 복기 - 리프 노드부터 특정 노드까지 최소로 지우는 법 (0) | 2023.12.17 |
---|---|
ABC 332 D번 복기 - 행,열 바꿔서 다른 2차원 배열과 똑같이 만드는법(BFS가 된다고?) (0) | 2023.12.11 |
ABC 328 C번 복기 - 문자열에서 누적합을 떠올려야 할 때 (0) | 2023.11.12 |
ABC 328 E번 복기 cost를 modulo로 나눈 최소 스패닝 트리 구하는법 (0) | 2023.11.12 |
ABC 324 - D번 복기, 순열 다루는 테크닉 - 두 문자열이 서로의 순열일려면? - (0) | 2023.10.15 |