Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[최소 신장 트리] 6월 22일 #18

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions 05월 31일 - 최소 신장 트리/1244.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import sys #sys 모듈
input = sys.stdin.readline #입출력 향상 코드

"""
[스위치 켜고 끄기]

남학생 -> 해당 번호의 배수에 해당하는 스위치 상태 바꾸기
여학생 -> 해당 번호를 중심으로 좌우 대칭이면서 가장 많은 스위치 포함하는 구간의 상태 모두 바꾸기

좌우 대칭 검사 시, 스위치 범위 주의 (주어진 스위치 범위 넘어가지 않도록)
스위치 20개씩 출력하는 부분 주의
인덱스 번호 주의
"""

# 남학생의 스위치 바꾸기
def change_switch_boy(k, n, switch):
# k-1 부터 -> 인덱스는 0부터니까
for i in range(k-1, n, k): #k의 배수 스위치
switch[i] = 1 - switch[i] #스위치 상태 바꾸기
return

# 여학생의 스위치 바꾸기
def change_switch_girl(k, n, switch):
k -= 1 # 인덱스는 0부터
idx = 0 # 대칭 구간
# 스위치 범위가 넘어가거나 좌우 대칭이 깨질 때가지
while k-idx >= 0 and k+idx < n and switch[k-idx] == switch[k+idx]: #스위치 범위 안이고 스위치가 대칭이면
switch[k-idx] = switch[k+idx] = 1 - switch[k+idx] #스위치 상태 바꾸기
idx += 1 #다음 대칭으로
return

# 입력
n = int(input()) #스위치 개수
switch = list(map(int, input().split())) #스위치 정보
k = int(input()) #성별 정보 개수

for _ in range(k): #k번 반복
a, b = map(int, input().split()) #성별, 스위치 번호
if a == 1: #남자면
change_switch_boy(b, n, switch) #남학생의 스위치 바꾸기
else: #여자면
change_switch_girl(b, n, switch) #여학생의 스위치 바꾸기

# 출력
for i in range(0, n, 20): #스위치 20개 단위로
print(*switch[i:i+20]) #출력하기
78 changes: 78 additions & 0 deletions 05월 31일 - 최소 신장 트리/16202.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import sys #sys 모듈
input = sys.stdin.readline #입출력 향상 코드

"""
[MST 게임]

MST 알고리즘을 여러 번 실행해도 될까?
1. 크루스칼 알고리즘의 시간 복잡도는 O(ElogE)
이는 오직 간선을 정렬하는 연산의 시간 복잡도!
즉, 모든 간선을 한 번 정렬해서 저장해두면 이후 몇 번의 알고리즘을 수행하여도 연산 시간에 큰 영향이 없음
2. 간선 재사용을 위해 우선순위 큐가 아닌 배열에 저장하고 크루스칼 알고리즘 k번 실행
3. 매번 크루스칼을 수행할 때마다 제일 먼저 추가한 간선을 제외함
-> 첫번째 간선은 모든 점이 분리된 상태에서 들어오기 때문에 무조건 사용하게 되어 있고, 이는 사용한 간선 중 가장 짧은 간선
-> 제외될 간선은 배열의 첫번째 간선부터 순차적 제외
4. 만약 한 번 MST를 만들 수 없다는게 확인됐다면 이후에도 MST를 만들 수 없음
"""

# find 연산
def find_parent(x):
if parent[x] < 0: #x가 루트 노드면
return x #x 반환

parent[x] = find_parent(parent[x]) #루트 노드 찾아서 parent[x]에 대입
return parent[x] #parent[x] 반환

# union 연산
def union(x, y):
px = find_parent(x) #x의 부모 노드 찾아서 대입
py = find_parent(y) #y의 부모 노드 찾아서 대입

if px == py: #동일한 루트 노드면
return False #합칠 수 없음

if parent[px] < parent[py]: #py가 더 적은 자식 노드 가지면
parent[px] += parent[py] #py를 px에 합침
parent[py] = px #py의 부모노드를 px로 변경
else: #아니면
parent[py] += parent[px] #px를 py에 합침
parent[px] = py #px의 부모노드를 px로 변경

return True #합칠 수 있음

#크루스칼
def kruskal(n, m, edge, turn):
cost = 0 #비용
cnt = 0 #연결 횟수
for w in range(turn, m+1):
u, v = edge[w] #u,v정보를 edge[w]에서 받아옴
if not union(u, v): #합칠 수 없으면
continue #다음 반복문으로

cost += w #w를 cost에 더함
cnt += 1 #cnt 증가

if cnt == n-1: #n-1회 반복했으면
return cost #cost 반환

return 0 #0 반환

n, m, k = map(int, input().split()) #정점의 수, 간선의 수, 턴 수

edge = [None] + [tuple(map(int, input().split())) for _ in range(m)] #간선 정보

result = [] #결과 리스트

for turn in range(1, k+1): #k번 반복
# 초기화
parent = [-1]*(n+1)
# 연산
result.append(kruskal(n, m, edge, turn))

if result[-1] == 0: # 이후의 턴은 모두 0점이므로
break #반복문 종료

result += [0]*(k-len(result)) #이후 턴들의 점수 저장

# 출력
print(*result)
67 changes: 67 additions & 0 deletions 05월 31일 - 최소 신장 트리/1647.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import sys #sys 모듈
input = sys.stdin.readline #입출력 향상 코드

"""
[도시 분할 계획]

마을을 두개로 분리하고, 각 집끼리 이동할 수 있는 최소한의 도로만 남기는 문제
즉, 2개의 최소신장트리를 만들어야 하는 문제
-> 하나의 최소신장트리를 만들고, 그 중 가장 유지비가 큰 도로를 삭제
-> 크루스칼 알고리즘에서 가장 마지막에 삭제되는 도로가 유지비가 가장 큼
-> 크루스칼 알고리즘에서 간선을 n-2개만 선택하여 그 합을 구하여 해결
"""

# find 연산
def find_parent(x):
if parent[x] < 0: #x가 루트 노드면
return x #x 반환

parent[x] = find_parent(parent[x]) #루트 노드 찾아서 parent[x]에 대입
return parent[x] #parent[x] 반환

# union 연산
def union(x, y):
px = find_parent(x) #x의 부모 노드 찾아서 대입
py = find_parent(y) #y의 부모 노드 찾아서 대입

if px == py: #동일한 루트 노드면
return False #합칠 수 없음

if parent[px] < parent[py]: #py가 더 적은 자식 노드 가지면
parent[px] += parent[py] #py를 px에 합침
parent[py] = px #py의 부모노드를 px로 변경
else: #아니면
parent[py] += parent[px] #px를 py에 합침
parent[px] = py #px의 부모노드를 px로 변경

return True #합칠 수 있음

#크루스칼
def kruskal(n, edge):
cost = 0 #비용
cnt = 0 #횟수
for u, v, w in edge: #edge에서 u,v,w 가져옴
if not union(u, v): #합칠 수 없으면
continue #다음 반복문으로

#합칠 수 있으면
cost += w #w를 cost에 더하기
cnt += 1 #cnt 증가

if cnt == n-1: #n-1회 수행시
return cost #cost 반환

return 0 #0 반환

#입력
n, m = map(int, input().split()) #집의 개수, 길의 개수

edge = [tuple(map(int, input().split())) for _ in range(m)] #(A,B,C)

# 초기화
parent = [-1]*(n+1)

edge.sort(key=lambda x:x[2]) # 정렬

# 연산 & 출력
print(kruskal(n-1, edge))
85 changes: 85 additions & 0 deletions 05월 31일 - 최소 신장 트리/1774.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import sys #sys 모듈
input = sys.stdin.readline #입출력 향상 코드

"""
[우주신과의 교감]

4386번 : 별자리 만들기의 응용 문제
이미 연결된 정점들이 존재한다는 것을 제외하고는 4386번과 동일

1. 임의의 두 별에 대한 거리(간선) 모두 구하기
2. 이미 존재하는 통로들 표시
!주의! 통로의 개수가 m개라면 v-m-1개의 간선만 더 추가하면 될까?
이미 연결된 통로들도 사이클을 이룰 수 있기 때문에 유니온 연산을 하며 사이클 없이 연결된 간선만 세기
3. 이미 연결된 통로의 수를 k개라고 하면 v-k-1개의 간선을 추가로 선택
"""

# find 연산
def find_parent(x):
if parent[x] < 0: #x가 루트 노드면
return x #x 반환

parent[x] = find_parent(parent[x]) #루트 노드 찾아서 parent[x]에 대입
return parent[x] #parent[x] 반환

# union 연산
def union(x, y):
px = find_parent(x) #x의 부모 노드 찾아서 대입
py = find_parent(y) #y의 부모 노드 찾아서 대입

if px == py: #동일한 루트 노드면
return False #합칠 수 없음

if parent[px] < parent[py]: #py가 더 적은 자식 노드 가지면
parent[px] += parent[py] #py를 px에 합침
parent[py] = px #py의 부모노드를 px로 변경
else: #아니면
parent[py] += parent[px] #px를 py에 합침
parent[px] = py #px의 부모노드를 px로 변경

return True #합칠 수 있음

#크루스칼
def kruskal(n, edge):
cost = 0 #비용
cnt = 0 #횟수
for u, v, w in edge: #edge에서 u,v,w 가져옴
if not union(u, v): #합칠 수 없으면
continue #다음 반복문으로

#합칠 수 있으면
cost += w #w를 cost에 더하기
cnt += 1 #cnt 증가

if cnt == n-1: #n-1회 수행시
return cost #cost 반환

return 0 #0 반환

# 입력
n, m = map(int, input().split()) #우주신 개수, 통로 개수
position = [tuple(map(int, input().split())) for _ in range(n)] #좌표
edge = [] #통로의 길이 저장하는 리스트

for i in range(n):
for j in range(i):
dx = position[i][0] - position[j][0] #x좌표 차이 계산
dy = position[i][1] - position[j][1] #y좌표 차이 계산

edge.append((i, j, (dx**2 + dy**2)**(1/2))) #통로 쌍과 길이 edge에 저장

# 초기화
parent = [-1]*(n)

cnt = 0 #연결 횟수

for _ in range(m): #이미 연결된 통로 정보
u, v = map(int, input().split()) #입력 받음
# 이미 연결한 통로
if union(u-1, v-1): #연결할 수 있으면
cnt += 1 #cnt 증가

edge.sort(key=lambda x:x[2]) # 정렬

# 연산 & 출력
print("%.2f" %(kruskal(n - cnt, edge)))