결과 및 총평
어제 오후 3시에 거의 바로 시작했고, 저녁 먹기 전에 끝내려고 했다. 약간 설렁설렁했는데, 어쨌든 6시 전에 올솔브에, 전부 한번에 맞았다. 그래서 기분 좋게 저녁 먹으러 갔었다.
종료 후 전체 솔브 수는 차레로 1305/661/315/232/147였다.
솔브 수에서 보이듯 예년 보다 전체적으로 난이도가 쉬웠다. 작년 Round2 제출자가 500명을 살짝 넘은 것으로 보이는데, 올해는 1,2번 모두 만점 받은 사람이 500을 훨씬 넘긴 것을 보면, 어떻게 될진 모르겠지만 3번까지 긁기는 해야 안심할 수 있지 않을까 싶다.
아래는 각 문제의 간단한 풀이와 코드이다. 문제 지문은 나중에 codeground의 practice에 올라올 테니, 여기선 독자가 문제를 읽었다고 가정한다.
1. 친구들
각 사람을 정점으로 생각하고, 친구 관계를 간선으로 이어진 것이라 생각하면 결국 이 graph에서, connected component는 완전 그래프(perfect graph)가 된다. 따라서 연결 컴포넌트 개수가 곧 정답이 된다. 이는 union-find나 dfs로 쉽게 구현 가능하다. 나의 경우 dfs를 사용해 컴포넌트 수를 세었다. connected component만 판단하면 되니, 봐야 하는 간선은 각 $i$에 대해 $i$와 $i+D_{i}$를 있는 간선 밖에 없다.
1 |
|
2. 이진수
살짝 귀찮았던 문제, 결국 각 $b_{i}$는 $a_{i-t} or a_{i+t}$로 정해진다는 것을 알 수 있다. 그러므로 역추적을 위해서 다음과 같이 하면 된다.
- $b_{i} = 0$인 경우, $a_{i-t},a_{i+t}$ 모두 (존재한다면) 0으로 정한다.
- $b_{i} = 1$이고, $a_{i-t},a_{i+t}$ 중 한 쪽이 없거나, 한 쪽이 0으로 정해진 경우, 다른쪽엔 1로 값을 정한다.
- $a_{i}$ 중 아직 정해지지 않은 값들에 대해서, greedy하게 앞에서 부터 순서대로 보면서, 아무렇게 해도 상관없다면 (그런 문자열이 존재한다면) 0으로, 반드시 1로 해야 한다면 1로 정해준다.
자세한 부분은 구현을 참고.
1 |
|
3. No Cycle
그냥 가능한 정답을 묻는다면, 전체를 위상 정렬 시킨 후, 각 정점의 위상정렬상에서의 순서만 따져서 정답을 작성해도 된다. 하지만 이 문제에선 사전순으로 최소라는 조건이 붙으므로, 위와 같은 방식은 WA를 낸다. 다시 제한을 보면 $N \leq 500$, $M, K \leq 2000$으로 그렇게 크지 않다는 것을 알 수 있다. 그러니 각 간선의 방향을 결정할 때 마다 간선을 추가시켜 주면서, 지금 현재 정방향(결과에서 0으로 나타나는 방향)으로 추가가 가능한지 보고, 가능하면 정방향으로, 그렇지 않다면 역방향으로 간선 방향을 정해주면 된다. 이때 가능한지 판단은 dfs를 통해 했다.
1 |
|
4. 예약 시스템
이 문제의 핵심은 $p(c) = w_{a}+w_{b}$이므로, 그룹 마다 스트레스를 따로 따로 생각할 수 있단 점이다. 결국 총 페널티는 각 그룹에 대해, 그룹에서 다른 그룹과 이웃한 개수가 몇 개인지에 따라 달려 있다. 간단히 생각해 보면 같은 그룹 끼리 뭉쳐 있을 수록 페널티가 작아질 것이라 생각할 수 있다. (문제를 풀 당시엔 이렇게 생각했는데, 반례가 존재한다고 한다. 그리고 문제도 수정됐다…) 짝수개일 때는 직사각형 모양, 홀수일 경우 직사각형에서 정사각형 하나가 붙어 있는 모양이다. 이때 그룹의 인원 수가 작으면 계산이 달라질 수 있지만, 다행히 각 그룹 인원 수가 5이상이기 때문에, 달라지지 않는다.
이제 남은 것은 적절한 case-work이다. 양 끝에 있는 그룹의 인원수가 홀수/홀수 인 경우, 짝수/짝수 인 경우, 홀수/짝수 인 경우로, 나누어, 각각의 최솟값을 구해서 전체 최솟값을 구하면 된다. 제한에서 홀수인 그룹의 개수는 짝수가 될수 밖에 없음에 유의하라.
자세한 부분은 역시 코드를 참고.
1 |
|
5. 차이
가장 어려운 문제이면서도, 가장 전형적인 문제 였다고 생각한다.
문제의 어투(?)에서 느낄 수 있듯이 1번 쿼리에서, 정점 $i$와 $j$를 간선으로 있는 것처럼 생각할 수 있다. 물론 방향에 따라 가중치의 부호는 달라진다.
이제 2번 쿼리에서, NC인지 아닌지 부터 판단해보자. 어떤 정점 $i$와 $j$가 하나의 connected component 안에 있다면, 문제에서 주어진 것처럼 경로를 구성할 수 있음을 알 수 있다. 따라서, 하나의 connected component안에 있다면, NC가 아니고, 그렇지 않다면 NC 이다.
다시 1번 쿼리로 돌아가서, $i$와 $j$를 이으려 하는 상황을 보자. $i$와 $j$가 다른 connected component 안에 있으면 그냥 이어주면 된다. 같은 connected component라면 이제, $X_{i}-X_{j}$를 알아낼 수 있으므로, 업데이트 하려는 정보와 구한 정보를 비교한다. 만약에 같다면, 앞으로도 유일하게 결정 가능하므로 넘어간다. 하지만 다르다면, 지금 component내의 임의의 정점 $a,b$에 대해 $X_{a}-X_{b}$가 유일하게 결정되지 않는다. 따라서, 이 component에서 유일하게 결정할 수 없다고 표시해주어야 한다. 이후 2번 쿼리가 들어왔을 때, 같은 component 안에 있기는 하나 그 component에 이 표시가 되어 있다면 CF를 출력하면 된다.
이제 NC,CF 도 아닌 경우에 어떻게 구하는지 본다. 먼저 1번 쿼리에서 $i$와 $j$를 이을 때, 같은 component라면 간선을 추가할 필요가 없다는 것을 쉽게 알 수 있다. 따라서, graph의 형태는 forest가 된다. 쿼리들을 online으로 처리하는 것이 강제된다면 link-cut tree 같은 어려운 자료구조를 써야하지만, 지금같이 모든 정보가 주어지는 경우에는 그럴 필요가 없다. 최종 forest의 형태가 주어져 있으므로, 그에 따라 구성하면 된다.
구현하는 방법에는 여러 가지가 있겠지만, 나의 경우 전체 forest를 0번 정점을 추가해서 하나의 tree로 만든 후, dfs ordering + indextree를 사용해서 구했다. 그 외에 NC, CF 등의 판단은 union-find를 사용했다. union-find에서 union을 할 때 conflict 정보를 넘겨주어야 함에 유의하라.
자세한 구현은 코드 참고.
1 |
|