프로그래밍 연습장

ICPC Yokohama Regional 2018 본문

문제 해결

ICPC Yokohama Regional 2018

김준원 2020. 7. 20. 02:22

오랜만에 셋을 돌았다. rkm0959와 2인 팀으로 돌았다. 문제들이 대체적으로 마음에 들었지만, 그냥 마음이 아프다. 업솔빙한 문제들의 풀이를 간략하게 정리해 본다.

A Digits Are Not Just Characters

더러운 문제다. 생략.

B Arithmetic Progressions

보자마자 "아 ㅋ 10초 컷이네 ㅋ" 생각해서 키보드를 잡자마자 그 풀이가 O(N3)임을 깨달았다. 20초 더 생각하니 O(N2)더라. 내 앞에 있던 놈도 그런 거 같았다.

a1a2로 쓸 두 개의 수를 정하면 만들 등차수열이 정해지고, 등차수열이 정해졌다면, 주어진 수열에서 등차수열을 몇 번째까지 뽑을 수 있는가는 쉬운 연습문제이다. 이를 단순히 구현하면 O(N3)이다. 여기서, DP처럼 같은 (a1,a2)의 쌍을 한 번만 세도록 처리해주자. 이 부분을 잘 이해하기 힘들다면, DP같이 처리한다고 생각하자. dp(i,j)를, a1=vi, a2=vj로 두었을 때 만들 수 있는 등차수열의 길이로 두자. vk=a3=a2+(a2a1)이 되는 k가 존재한다면 dp(i,j)=1+dp(j,k)가 된다. 이제, 중복된 (i,j)에 대해 한 번만 셀 수 있으며, O(N2)의 시간 복잡도에 문제를 해결할 수 있다.

C Emergency Evacuation

다른 사람의 방해가 없을 때, 각 사람이 출입문까지 도달하는 최소 시간(i.e. 거리)는 쉽게 알 수 있다. ti=|Wxi|+|Hyi|+2일 것이다. 그냥, 각 사람이 그 시점에 출입문 앞에 뿅 하고 나타나서 줄을 선다고 생각하자. 그리고는 1초에 한 명씩만 내보내준다고 생각하자. 이 경우, 문제의 풀이는 자명하다. t1,...,tN을 오름차순으로 정렬하고, 각 i=2,...,N에 대해 ti=max를 수행하면 된다. 이 때 마지막 사람은 연산이 끝난 후 t_N의 시간에 나가게 될 것이다.

그렇지만, 원래 문제에서는 제약사항이 좀 더 많았으니 답이 좀 더 크지 않을까? 사실은, 원래 문제의 답도 같다! 이 사실은, 거꾸로 사람들을 들여보내주는 상황을 생각하면 쉽게 납득이 된다. 사람들을 출입문에서 1초에 한 명씩 차례대로 들여보내야 하는 상황이라면, 가장 자리가 멀리 있는 사람부터 들여보내야 모두가 앉게 되는 시간이 빨라진다.

D Shortest Common Non-Subsequence

LCS 문제와 비슷해 보인다. 풀이도 LCS와 비슷할 것처럼 생겼다. dp[i][j]를, A[i...]B[j...]의 Shortest common non-subsequence의 길이라고 하자. 종이에 조금 끄적여보면 그럴듯한 점화식이 나온다. zero_S (i)를, S[i...]에 등장하는 첫 0의 위치로, one_S(i)S[i...]에 등장하는 첫 1의 위치로 두자. 그러면,

 

dp[i][j] = min(dp[zero_A(i)+1][zero_B(j)+1], dp[one_A(i)+1][one_B(j)+1])

 

이 된다. 유의. 만약 입력으로 빈 문자열 두 개가 들어온다면 답은 길이 1인 문자열 0이다. 그러니, (0-indexed notation으로) dp[N][M] \neq 0이고, 오히려 dp[N+1][M+1] = 0이다. A[N+1...]라는 말은, 빈 문자열과는 뉘앙스가 다른, "길이가 -1인 문자열"이며, A[N...]과는 오히려 구분해야 하는 것이다. 이 점을 "세심하게" 고려해서 세심한 DP식을 세워야 한다. 나는 이 문제를 풀면서 불길한 예감을 느끼면서도, 이 진실을 외면하고 사전순 최소로 복원해야 한다는 조건 탓을 했다. 고통스럽더라도 진실을 마주할 수 있는 용기가 필요하다.

E Eulerian Flight Tour

왜 맞지?

*

F Fair Chocolate-Cutting

*

G What Goes Up Must Come Down

수열에서 가장 작은 수는 가장 왼쪽 또는 가장 오른쪽으로 (언젠가는) 보내야 한다. 그리고, 그렇게 최소원소를 양쪽 끝 중 하나로 보내면, 남은 수열에 대한 문제는 완전히 독립적인 다른 문제이다. 그러면, 가장 작은 수를 왼쪽으로 보내야 할까, 오른쪽으로 보내야 할까? 그냥 가까운 쪽으로 보내면 장땡이다. 어느 쪽으로 보냈든 이후의 문제와는 별 상관이 없으니.

구현할 때에는 우선순위 큐, 펜윅 트리, 세그먼트 트리 등을 쓰면 된다.

H Four-Coloring

구사과님의 오색 정리 포스트를 참조한다. 이 글에서 오색 정리의 첫번째 증명을 간략히 리뷰한다:

 

  • 수학적 귀납법을 사용한다. 기저는 물론 참이다.
  • 증명은 무방향 평면 그래프에는 차수가 5 이하인 정점이 존재한다는 보조정리에서 시작한다. 이건 오일러 방정식에 의해 유도되고, 그냥 믿자, 지금은.
  • 그러한 정점 v를 하나 고르고, 그래프에서 v를 제외한 정점들을 모두 칠해본다. 귀납 가정에 의해 물론 가능하다.
  • v의 최대 다섯개인 이웃을 u_1, \cdots, u_5로 각도 순서대로 이름붙이자. 이 이웃들은 어떻게든 칠해져 있을 것이다.
  • 이웃들을 칠할 때 1번 색에서 5번 색까지를 모두 사용하지 않았다면 운이 좋은 것이다. 이웃들을 칠할 때 사용하지 않았던 색 하나를 칠하면 된다.
  • 운이 나쁘게 이웃들에게 쓰고 남은 색이 없다면 어떻게 할까? 그래도 괜찮다. 이웃들의 색을 적절히 재배치해서 여유분을 하나 남길 수 있다.
  • u_1, \cdots, u_5에 칠한 색을 차례로 c_1, \cdots, c_5라고 하자. 원래 그래프에서 c_1, c_3의 색으로 칠한 정점만을 남겼을 때, u_1u_3이 연결되어 있지 않다면, 그래도 운이 좋은 편이다. u_1의 색을 c_3으로 바꾸고, u_1 주변에 있는 c_3으로 칠해진 정점들의 색을 c_1으로 바꾸고, 그 주변의 정점들의 색을 c_3으로 바꾸고, 쭉 해 준다. u_1u_3이 연결되지 않은 경우이므로 이 과정은 적당히 굴러가다 끝난다.
  • 위 과정이 끝나면 u_1의 색이 c_3이 되면서도 올바르게 색을 칠하게 되었고, 그러면 v에는 c_1을 칠할 수 있게 되었다! 예~!
  • 정말 운이 나쁘다면..? ㅜㅜ 그러면 어쩔 수 없다. 대신, u_2u_4를 붙잡고 같은 짓을 해 보자.
  • 증명의 핵심은, 두 과정이 모두 실패할 수는 없다는 것이다. 즉, c_2, c_4의 색으로 칠한 정점만을 남겼을 때, u_2u_4는 무조건 분리되어 있다는 것이다!
  • 왜? 그래프가 평면 그래프이기 때문이다! 두 과정이 모두 실패했다면, c_1, c_3으로 칠해진 정점으로 u_1u_3을 이은 경로와, c_2, c_4로 칠해진 정점으로 u_2u_4를 이은 경로가 모두 존재한다. 그런데, 두 경로는 교차한다! 그러면 그 교차점은 c_1, c_3 중 하나로 칠해져야 하는 것인가, 아니면 c_2, c_4 중 하나로 칠해져야 하는 것인가? 여기서 두 과정이 모두 실패하면 모순이고, 두 과정 중 하나는 성공함을 보였다.

위 증명은 실제적으로 일반적인 평면 그래프를 오색으로 색칠하는 방법을 제시한다. 그런데, 문제에서 제시하는 평면 그래프는 더 강력한 성질을 가진다. 얘는 무조건 차수가 4 이하인 정점을 하나 가진다. 바로 그래프의 가장 오른쪽 아래에 있는 점이다. 얘를 가지고 위의 과정을 그대로 따라가면 입력으로 주어지는 형태의 그래프를 4개의 색으로 칠할 수 있다!!!(증명의 마지막 부분에서 u_5는 전혀 써먹지 않았다는 점을 확인하라.) 시간 복잡도는 O(N^2)로 별 문제없다.

I Ranks

*

J Colorful Tree

색안경을 끼고 c번 색의 관점에서만 문제를 보자. 사실 이 문제는 다음 세 가지 연산을 처리하는 문제와 같다.

 

  1. 정점 v에 표시를 한다.
  2. 정점 v에 한 표시를 지운다.
  3. 표시한 정점들을 모두 잇기 위해 필요한 최소 간선 수를 구한다.

3번 질의의 답은, 초기에는 0이고, 1번과 2번 질의에 따라 증가하거나 감소할 것이다. 3번 질의의 답을 질의가 들어올 때 계산하지 않고 1번과 2번 질의가 들어올 때 변화량을 잘 계산해줘서 슝슝 더하고 빼 줄 수 있지 않을까?

트리를 루트에 매달고, 가만히 생각해 보자. 만약 지금 매달아놓은 트리의 정점 몇 개에 표시가 되어 있다고 생각해 보자. 이들을 모두 잇는 간선들은 또 다른 트리처럼 생겼을 것이다. 얼핏 보면, 이들 전체의 LCA (l이라 두자) 에다가 표시한 정점들을 주렁주렁 매달아둔 것처럼 생겼다. 즉, 이들을 모두 잇는 간선들은 LCA에서 각 정점으로 가는 경로들의 합집합이다. 이제, 여기에 새로운 정점 x를 추가하면 3번 질의의 답은 얼마나 증가할까?

만약 새로 추가한 정점 xl의 자손이 아니라면, 정확히 xl 사이의 거리만큼 답이 증가한다. 만약... l의 자손이라면? 고른 정점들을 모두 잇기 위해서는 x에서 l로 가는 경로만큼을 결국은 추가해야 할 건데, 기존에 골라둔 간선들과 얼마나 겹치는지를 우리는 모른다. x에서 가장 가까운 '가지'를 찾아서, 그 가지까지의 거리를 재야겠다. 그러기 위해서 우리는 정점들에 DFS 순서를 매겨두고, 고른 정점들 중에서 x와 DFS 순서가 가까운 두 정점(왼쪽에서 하나, 오른쪽에서 하나)만 잡아서 실제 거리를 재보면 된다. 그 중 짧은 길이만큼을 3번 질의의 답에 더해준다.

삭제는 반대로 하면 된다. (진짜로!)

K Sixth Sense

편의상, 상대편이 가진 배열을 a, 내가 가진 배열을 b라고 하자. 사전순으로 최대인 배치를 출력하라는 조건이 없을 때에는 잘 알려진 그리디 알고리즘으로 풀 수 있다.

 

  1. ab를 정렬한다.
  2. a의 원소를 a[1], ..., a[N] 순서대로 보면서, ba[i]보다 큰 원소가 존재하면 사용해서 점수를 올린다.

(물론, a를 정렬하지 않아도 문제없다.)
이 방법은 O(N)이다.

사전순으로 최대인 배치를 출력해야 하는 게 좀 짜증난다. 첫번째 판에는 뭘 내야 할까? 첫번째 판에서 이길 수 있으면 언제나 이기는 게 이득이다. 사전순 최대를 고려하지 않는다면 a[1] 초과의 첫번째 수를 쓰는 게 이득인데, 이제 좀 애매해진다. "첫번째 수로 얘를 써도 여전히 최적해가 되는지"로 parametric search를 돌리자. 첫번째 수 후보를 \log N고려하게 되므로, O(N \log N)에 첫번째 원소를 결정할 수 있다. 물론, 전체 시간 복잡도는 O(N^2 \log N).

Comments