문제
오늘은 스타트링크에 다니는 사람들이 모여서 축구를 해보려고 한다. 축구는 평일 오후에 하고 의무 참석도 아니다. 축구를 하기 위해 모인 사람은 총 N명이고 신기하게도 N은 짝수이다. 이제 N/2명으로 이루어진 스타트 팀과 링크 팀으로 사람들을 나눠야 한다.
BOJ를 운영하는 회사 답게 사람에게 번호를 1부터 N까지로 배정했고, 아래와 같은 능력치를 조사했다. 능력치 Sij는 i번 사람과 j번 사람이 같은 팀에 속했을 때, 팀에 더해지는 능력치이다. 팀의 능력치는 팀에 속한 모든 쌍의 능력치 Sij의 합이다. Sij는 Sji와 다를 수도 있으며, i번 사람과 j번 사람이 같은 팀에 속했을 때, 팀에 더해지는 능력치는 Sij와 Sji이다.
N=4이고, S가 아래와 같은 경우를 살펴보자.
예를 들어, 1, 2번이 스타트 팀, 3, 4번이 링크 팀에 속한 경우에 두 팀의 능력치는 아래와 같다.
- 스타트 팀: S12 + S21 = 1 + 4 = 5
- 링크 팀: S34 + S43 = 2 + 5 = 7
1, 3번이 스타트 팀, 2, 4번이 링크 팀에 속하면, 두 팀의 능력치는 아래와 같다.
- 스타트 팀: S13 + S31 = 2 + 7 = 9
- 링크 팀: S24 + S42 = 6 + 4 = 10
축구를 재미있게 하기 위해서 스타트 팀의 능력치와 링크 팀의 능력치의 차이를 최소로 하려고 한다. 위의 예제와 같은 경우에는 1, 4번이 스타트 팀, 2, 3번 팀이 링크 팀에 속하면 스타트 팀의 능력치는 6, 링크 팀의 능력치는 6이 되어서 차이가 0이 되고 이 값이 최소이다.
입력
첫째 줄에 N(4 ≤ N ≤ 20, N은 짝수)이 주어진다. 둘째 줄부터 N개의 줄에 S가 주어진다. 각 줄은 N개의 수로 이루어져 있고, i번 줄의 j번째 수는 Sij 이다. Sii는 항상 0이고, 나머지 Sij는 1보다 크거나 같고, 100보다 작거나 같은 정수이다.
출력
첫째 줄에 스타트 팀과 링크 팀의 능력치의 차이의 최솟값을 출력한다.
예제는 여기서 확인하자.
https://www.acmicpc.net/problem/14889
그래도 백트래킹을 연습해서 그런지 이번 문제는 풀었다!
import math
N=int(input())
lst=[list(map(int,input().split())) for n in range(N)]
min_result=math.inf
def dfs(cnt,link_team, visited):
global min_result
total=set(range(N))
if cnt==N/2:
start_team=list(total-link_team)
link_team=list(link_team)
link_result, start_result=0,0
while link_team or start_team:
link_n=link_team[0]
link_team.remove(link_n)
for m in link_team:
link_result+=lst[link_n][m]+lst[m][link_n]
start_n=start_team[0]
start_team.remove(start_n)
for m in start_team:
start_result+=lst[start_n][m]+lst[m][start_n]
min_result=min(min_result, abs(link_result-start_result))
return
for n in total-link_team-visited:
link_team.add(n)
visited=set(range(n))
dfs(cnt+1,link_team,visited)
link_team.remove(n)
link_team=set()
visited=set()
dfs(0,link_team,visited)
print(min_result)
내가 푼 코드이다. 백트래킹 방식으로 진행했음에 뿌듯하다.
먼저 스타트팀과 링크팀의 인원들을 넣어준 후, N/2명이 되면 각팀의 능력들을 합해 빼주는 방식으로 진행된다.
코드의 dfs함수를 적용하면
if문을 제외한 for문이 돌아가는데, link_team에 사람을 한명씩 넣어준 후 백트래킹 방식으로 그 안에서 dfs함수를 진행한다.
그러면 for문의 for문의 for문이 된다.
for문의 동작 갯수가 점점 줄어든다. =total-link_team-visited(들어온 놈이 또 들어가면 안되니깐 = link_team, 순서가 없으므로 방문했던 것은 피해야 되니깐 = visited)
(초반에 visited를 넣어주지 않아 link_team의 요소만 빼주었더니 시간초과가 발생했다...)
예를 들어 6명의 인원이 있을 때,
dfs(cnt=0, link_team={}, visited={}) 작동
- for문 첫번째가 진행. n=0, dfs(cnt=1, link_team={0},visited={})
- for문 첫번째 진행(link_team을 빼줘서 1부터 시작) n=1, dfs(cnt=2, link_team={0,1},visited={0})
- for문 진행 n=2, dfs(cnt=3, link_team={0,1,2}) => if문 동작. 끝나면 link_team.remove()함수로 2를 제거
- for 문 진행 n=3, dfs(cnt=3, link_team=0,1,3}) => if 문 동작. 끝나면 3을 제거
- ~
- ~
- for문 두번째 진행. n=2, dfs(cnt=2, link_team={0,2}, visited={0,1})
- for문 진행(visited로 1을 뺏으므로) n=3, dfs(cnt=3, link_team={0,2,3}) =>if문 동작
- n=4
- n=5
- for문 세번째. n=3, dfs(cnt=2, link_team={0,3},visited={0,1,2}
- for문 n=4, dfs(cnt=3,link_team={0,3,4}) => if문
- n=5
- for문 네번째. n=4, dfs(cnt=2, link={0,4},vidisted={0,1,2,3})
- n=5, dfs(cnt=3,link={0,4,5}) => if문
- for문 첫번째 진행(link_team을 빼줘서 1부터 시작) n=1, dfs(cnt=2, link_team={0,1},visited={0})
- n=1, dfs(cnt=1, link={1}, visited={0})
- n=2, dfs(cnt=2, link={1,2}, visited={0,1})
- n=3, dfs(cnt=3, link={1,2,3})
- n=4, dfs(cnt=3, link={1,2,4})
- n=5, dfs(cnt=3, link={1,2,5})
- n=2, dfs(cnt=2, link={1,2}, visited={0,1})
이런 식으로 계속해서 동작한다. cnt=3을 동작하는 for문이 줄어드는 것을 볼 수 있다. = visited함수 때문
visited와 link_team이 이전에 방문했던 for문은 없애주고 남아있는 것만 돌아가게 한다.
또한 if문의 while문도 변경해줬다.
for i in range(cnt-1):
for j in range(i+1,cnt):
link_result+=lst[link_team[i]][link_team[j]]+lst[link_team[j]][link_team[i]]
start_result+=lst[start_team[i]][start_team[j]]+lst[start_team[j]][start_team[i]]
이렇게 이중 for문을 써서 remove함수를 쓰지 않게 해봤는데.... 메모리나 시간은 별반 차이가 없었다....ㅜ
수정된 코드이다.
import math
N=int(input())
lst=[list(map(int,input().split())) for n in range(N)]
min_result=math.inf
def dfs(cnt,link_team, visited):
global min_result
total=set(range(N))
if cnt==N/2:
start_team=list(total-link_team)
link_team=list(link_team)
link_result, start_result=0,0
for i in range(cnt-1):
for j in range(i+1,cnt):
link_result+=lst[link_team[i]][link_team[j]]+lst[link_team[j]][link_team[i]]
start_result+=lst[start_team[i]][start_team[j]]+lst[start_team[j]][start_team[i]]
min_result=min(min_result, abs(link_result-start_result))
return
for n in total-link_team-visited:
link_team.add(n)
visited=set(range(n))
dfs(cnt+1,link_team,visited)
link_team.remove(n)
link_team=set()
visited=set()
dfs(0,link_team,visited)
print(min_result)
'알고리즘' 카테고리의 다른 글
[백준 1904번] 01타일 in python (0) | 2021.11.04 |
---|---|
[백준 1003번] 피보나치 함수 - 0과 1 count하기 in python (0) | 2021.11.04 |
[백준 14888번] 연산자 끼워넣기 in python (0) | 2021.11.03 |
[백준 2580번] 스도쿠 in python (0) | 2021.11.03 |
[백준 9663번] N-Queen in python (0) | 2021.11.02 |
댓글