후기를 쓰다 보니 ICPC 25-26 APAC 후기를 안썼다는 것을 깨닫고 쓰고 나니 후기가 날아가서 포기하고 다시 돌아와서 씁니다. 저장하기를 생활화해야겠습니다. APAC 후기는 무기한으로 미루기로 했습니다.

 

UCPC 2026에 본선가면울릉도에서팀연습새벽2시에DP를하다니이상해라는 이름으로 출전했습니다.

 

새벽2시에DP를하다니이상해: 아는 자료구조가 많으나 지식에 비해 활용도가 낮습니다. EDPC를 전부 풀며 활용법에 대한 감을 키우긴 했으나 조금 부족합니다. 구현을 쉽게 저는 편이지만 그럼에도 불구하고 문제를 풀기 위해 공부해둔 잘 짜여진 템플릿을 제법 외우고 있습니다. 긴 구현이나 애드혹한 구현에 약하고 카운팅이나 off by one을 어려워하지만 usual한 구현이나 아이디어를 내는데 강점이 있습니다.

새벽2시에기하를하다니이상해: 기하를 잘합니다. 비슷한 의미로 흔히 차력쇼라고 하는 구현에 강점이 있습니다. 주력 언어가 파이썬이고 셋 중 짧은 구현이 가장 빠릅니다. 

새벽2시에구현을하다니이상해: 비슷하게 기하를 잘하지만 기하보다는 올라운더에 가깝습니다.

 

사실 팀 전력을 잘 알지 못합니다. 대충 서로 차력쇼를 하듯 비비다가 폭탄돌리기마냥 던지면 누군가 푸는 그런 팀 조합인 듯합니다.

 

가장 먼저 팀을 결성한건 DP/구현입니다. UCPC 콜포태를 받을 때부터 떨어지면 팀을 하자고 지나가듯 얘기했는데 둘다 까먹지 않고 팀이 됐습니다. 동시에 같은 처지인 기하와 팀을 만들었습니다. 

 

구현이 예선이 있는 주에 해외 일정이 있고 특히 예선 당일인 토요일에 귀국을 하는 일정이었기 때문에 조기퇴실해야 했습니다. 

 

대회 직전 화요일에 UCPC 2021 예비소집 연습 셋을 DP/기하가 돌았습니다. P21J의 센트로이드 분할정복 풀이를 거의 완벽하게 떠올렸지만 구현 디테일에서 몇가지 빼먹는걸 보고 제법 불안했습니다. 기하도 누적합에서 실수가 있었기 때문에 더 그랬습니다마는 그럼에도 불구하고 성적이 아주 나쁘지는 않아 한편으로 안심되기도 했습니다.

 

예비소집 입장이 13시부터였고 구현이 13시에 도착하지 못할 것이라고 미리 얘기했습니다. 13시에 디스코드에 도착하니 기하가 이미 도착해있었습니다. 둘이 먼저 전략에 대해 얘기하고 있었습니다. 기하가 쉬운 구현에서 저보다 빠르기 때문에 A를 빨리 밀고 균등하게 보면 되겠다고 했고, 제가 L부터 거꾸로 읽기로 했습니다. 13시 30분쯤 구현이 도착해 EF부터 읽기로 얘기하고 시작했습니다.

 

 

A(00:01, +0) 기하가 A를 아주 빠르게 해결했습니다. 나머지 둘은 문제를 읽지 못했습니다. 동시에 구현이 E/F/G를 읽고 제가 L/K/J를 간단히 훑어봤습니다. 

 

C/I(00:03) 구현이 C가 솔브가 나왔다는걸 빠르게 파악하고 기하가 구현에 들어갔습니다. 그때 저는 I를 읽고 간단한 패리티 조건분기 문제로 판단하고 간단한 구현에 들어갔습니다. 예선 전날 투스텝 문제에 대해 얘기를 좀 했는데, solved.ac 다이아 수준의 애드혹 문제가 나올 것이라고 예상했지만 실제 난이도는 그보다 더 낮았다고 합니다. 덕분에 투스텝에 대한 부담 없이 셋을 돌 수 있었습니다.

 

C(00:06, +0) 기하가 솔브했습니다. 마찬가지로 무슨 문제인지 읽지 못했습니다. 

 

D/I(00:07) 기하가 짧은 구현으로 해결할 수 있을거라고 했습니다. 그때 저는 I가 예제가 잘 나오지 않는다는 것을 깨달았습니다. 첫 관찰이 짝수번째 합과 홀수번째 합의 차가 일정하게 유지된다는 것이었는데 그것 이상의 다른 추가적인 관찰이 보이지 않아 포기하고 솔브가 나온 E를 읽었습니다. 구현이 다익스트라 같은 느낌이라고 해서 그래프 문제임을 어렴풋이 알고 시작했습니다. 한편으로 팀과의 소통에서 구현에게 I를 봐달라고 요청했습니다.

 

D/E(00:08) E번의 풀이를 어렴풋이 깨달았습니다. 문제를 읽으면서 그래프가 연결 그래프가 아니면 모두 연결될 수 없을지도 모르는데 하는 생각을 잠깐 하고 나니 모든 정점에 공항을 지으면 연결 그래프가 된다는 것을 알았고, 비슷한 시점에 MST가 떠올랐습니다. 같은 시각 기하가 D번의 풀이를 가져왔습니다. 다만 증명 없이 코드를 짰기 때문에 검토해야했습니다. 동시에 구현이 I의 그리디 풀이를 생각했습니다.

 

맞음

 

D/I(00:10) D의 검토를 하다 말고 솔브수가 많은 것을 보고 기하가 그대로 제출해서 맞았습니다. 이정도로 쉬운 문제가 아니라면 이 시간에 이렇게 많이 풀리기 어렵다는 계산이었습니다. 한편 구현은 I가 WA가 많이 쌓인 것을 보고 풀이를 재검토하기 시작했습니다. 기하구현이 동시에 검토하고 반례를 만들기 시작했습니다. 저는 음성 채팅으로 검토에 동참함과 동시에 E를 구현하고 있습니다. 

 

E/I(00:15) 이 시점에 E번이 MST를 두번 하도록 구현하는 것이 좋다는 판단을 했습니다. 동시에 I의 제한을 수상하게 여기고 구간 dp를 사용하는 $O(N^2)$의 풀이가 있지 않겠냐고 제안했습니다. 결과론적으로 잘못된 판단이었습니다.

 

E(00:17, +0) 무리 없이 구현에 완료하고 1솔브를 챙겼습니다. 기하가 I를 포기하고 L으로 넘어갔습니다. 

 

I/L(00:22) 저는 다시 I로 돌아왔습니다. 기하가 L의 형태를 정리해왔습니다. 증가하는 부분수열의 길이마다 개수가 필요하다고 했습니다. I를 고민하다 말고 L의 dp식을 잠시 고민했습니다. 값이 증가해야한다는 점을 이용해 세그먼트 트리를 $N$개 이용하는 풀이를 가져왔습니다. EDPC같은데서 볼 법한 유형이었고 최근에 비슷한 유형을 접해봐서 쉽게 방향을 잡을 수 있었습니다. 하지만 설명하는게 오래 걸렸습니다. 몇번 같이 연습해보지 않아서 의사소통이 엄청 원활하지는 않았던 것 같습니다. 비슷한 시각 I의 풀이를 시뮬레이션으로 거의 확신했습니다. 모든 값을 어떤 고정된 목표값 $x$가 되도록 만들 수 있는지 묻는 문제는 어렵지 않게 해결할 수 있음을 팀원 모두가 알고 있었습니다. $x$를 가능한 모든 범위에 대해서 돌려 보면 된다는 결론을 내리고 많아야 ```max(A)``` 정도이지 않나 싶어서 구현을 시작했습니다. 동시에 기하가 K를 읽어보기 시작했습니다.

 

K(00:28) 구현이 K를 읽기 시작했습니다. 트리 dfs 왕복 사이클을 돌듯이 순회하면 될 것이라는 직관을 설명했습니다. 비유를 오일러 투어로 들었지만 결과론적으로 잘못 든 비유였던 것 같습니다. 현장에서 만나서 했으면 그림을 그려서 보여줬을 듯 합니다. 

 

I(00:31, -1) I를 틀렸습니다. 대회가 끝나고 나온 얘기 중 하나는 I의 목표값의 최솟값의 상한이 100만정도까지 될 수 있다는 것이었습니다. 이걸 발견하지 못했고, 오랜 시간 물려있었습니다.

 

I(00:43, -2) 결국 못참고 목표값의 상한을 5000으로 수정해서 제출 후 틀렸습니다. 각 목표값마다 $O(N)$의 시간이 걸리는 풀이였기에 이보다 더 클 수 없다는 점을 깨닫고 다른 풀이로 돌아가야한다는 생각을 했습니다.

 

L(00:45) 기하가 maximal한 증가하는 부분수열의 개수가 필요하다고 했습니다. 이 시점에서 I/K/L을 세 명이 세 문제를 동시에 보면서 정신없이 지나갔습니다. 결과적으로는 K는 어려웠고, I는 번아웃이 왔고, L은 식 정리가 침착하게 되지 않았습니다. 마음이 급해 빠르게 풀고 다음 문제를 보려는 조급함이 독이 되지 않았나 싶습니다. 기하가 I의 코드를 동시에 읽으면서 제가 K의 가능한 경우를 그려봄과 동시에 구현이 L의 풀이를 고민하고 있었습니다. 각자 자기가 잡은 문제가 아닌 문제를 동시에 컨트롤하고 싶었던 것 같습니다.

 

L(01:07) 구현이 L의 풀이를 내왔습니다. 제가 구한 증가하는 부분수열의 개수를 믿고 그 다음부터 자연어 풀이를 써왔습니다. 하지만 여전히 maximal에서 이슈가 있습니다. 

 

I(01:14) 기하가 이분 탐색 아이디어를 가져왔습니다. 다만 단조성이 없다는 사실을 금방 깨달았고 이분 탐색 아이디어를 무시했습니다. 결과적으로 옳았습니다.

 

L(01:16) 구현이 본인의 풀이를 손으로 계산해 예제가 옳게 나옴을 확인했습니다. 구현은 일정 상 중도퇴실해야했기에 기하가 구현했습니다. 이 즈음 구현이 퇴실했습니다.

 

I(01:21, +2) 어느 시점이었는지 잘 기억나지 않지만 I의 풀이를 알았습니다. 맨 처음에 생각했던 홀수번째 합과 짝수번째 합의 차가 항상 일정하다는 점을 이용하면 됐습니다. 기하가 $N$이 짝수일 때 목표 값을 무한히 큰 정수 하나만 봐도 된다고 했던 것을 듣고 케이스워크를 나눠서 코드를 짰는데, 홀수일 때 목표치를 잡을 수 있음을 그 이후에 깨달았습니다. 열심히 구현해서 맞았습니다.

 

 

L(01:20, -1) 비슷한 시각 기하가 L을 구현하고 TLE를 받았습니다. 기하는 분명 파이썬이 느려서 시간 내에 돌지 않는 것이라고 판단해 저에게 C++ 번역을 부탁했습니다. 

 

L(01:33) 번역을 완료하고 예제가 나오지 않는 점을 확인했습니다. 펜윅 트리에서 without return 오류가 있었습니다. 사용 중인 TDM 컴파일러는 웬만한 곳에서 그냥 실행시키기 때문에 에러메시지 없이 틀린 값을 받아 문제가 생겼습니다. 런타임 에러의 종류는 채점기가 알려주지 않지만 기하가 디버깅 해서 발견했습니다.

 

L(01:37, -2) 리턴 문구를 삽입 후 제출해 WA를 받았습니다. 

 

K(01:43) 기하가 K를 건드리기 시작했습니다. 이전에 그려둔 그림을 통해 모든 정점의 간선 위치에 따라 최대 5가지 패턴이 나옴을 설명해줬습니다. 

 

L(01:46, +2) 파이썬 bigint를 생각하면 C++에서는 모듈로 연산을 더 자주해줘야 합니다. 예제1이 옳게 나오면 그 문제 뿐이겠거니 싶어 모듈로 연산을 코드 처음부터 끝까지 10억이 넘어갈 수 있는 모든 위치에 해줬고 특히 뺄셈의 경우 더 조심히 보정해 맞았습니다. 스코어보드를 보고 B로 넘어갔는데, 넘어가면서 기하에게 J를 확인해달라고 부탁했습니다. 결과적으로 좋은 판단이었습니다. I에서 멈춰있을 때 뇌를 환기하기 위해 J를 잠시 끄적여놨는데, 구간별로 정렬하고 범위가 겹치는 구간을 잘 비벼넣기정도로 설명해줬는데도 뭔가 하기 시작했습니다.

 

B(02:04) 중요한 관찰을 했습니다. 가능한 제출 시각의 최솟값을 구하라는 것으로 보아 이분 탐색이리라 생각했습니다. 단조성에 대한 증명을 하자마자 문제를 빠르게 환원했습니다. 결국 구간이 있을 수 있는 범위가 주어지면 겹치지 않게 배치할 수 있냐는 문제가 되었습니다.

 

B/J(02:13, 0) 두 문제가 거의 동시에 풀렸습니다. 제가 B, 기하가 J를 잡고 둘다 서로 조용히 구현을 했던 것 같습니다. J는 제가 정확한 작동을 모르고, B는 구간이 있을 수 있는 범위의 상한이 모두 같다는 점을 이용하면 뒤에서부터 그리디하게 채울 수 있음을 알았습니다. 이 시점에서 8솔브를 확인하고 상위 20위 이내에 들어갈 것이라고 거의 확신했습니다. 동시에 여기서 그만 풀어도 되겠다고 얘기했습니다. 하지만 시간이 40분 이상 남았다는 점과 최상위권을 노려보고싶었기 때문에 다른 문제를 풀기로 했습니다. 

고려해볼만한 문제는 G 또는 K였는데, 프리즈 이후였기 때문에 두 문제 중 어느 문제가 많이 맞은 문제인지 알 수 없었습니다. 이전에 계속 봐왔던 K를 보기로 했습니다.

 

K(02:15) 기하가 그래프의 중요한 성질 중 하나인 차수 4인 정점은 3 이상인 정점과 인접할 수 없음을 확인했습니다. 그걸 듣자 마자 차수 3인 정점끼리 인접한다면 어떤 형태로 붙어있어야 한다는 것을 정리하고 가능한 순회 방법을 그렸습니다. 이때 그림을 서로 보여주기 곤란했다는 점이 어려웠습니다.

K(02:19) B/J 솔브 이전에 했던 관찰 중 하나는 트리 간선 별 회로의 진입/진출 위치에 대한 정리였습니다. 이를 토대로 트리 dp를 한다면 가능성 판정 정도는 할 수 있지 않나 얘기했고 기하가 구현을 시작했습니다.

K(02:42) 기하가 구현을 마치고 예제를 요구했습니다. 추가로 문제를 더 풀기 곤란한 시점이라고 생각했어서 더 할 일이 없던 차였기 때문에 예제를 만들려고 시도했습니다. 마침 본문에 있던 그림이 가능한 예제였기 때문에 본문에 있던 예제를 입력 형식에 맞춰 전달했습니다.

K(02:47) 본문에 있던 예제의 실제 해를 손으로 구성해봤습니다. 손으로 구성하며 확인한 점은 임의로 진입/진출을 매핑한다고 해서 그에 맞는 회로가 존재하지 않을 수 있다는 점이었습니다. 이 시점에서 이런 형태로 풀기 위해서는 사용할 수 있는 모든 진입/진출 위치에 대해서 타일을 전부 가지고 있고 각 정점이 어떤 타일의 어떤 방향으로 마무리했을 때를 저장한 뒤 dp 과정에서 이를 모두 계산해줘야 했다는 것을 깨달았습니다. 이 방법으로 해결한 팀이 있다는 것 같았습니다. 차수 3인 정점이 문제였는데, 사용할 수 있는 타일이 10가지 내외였을 것이라고 파악했기 때문에 아주 못풀 방법은 아니었던 것 같습니다마는 남은 시간 내에 해결할 수 없다고 판단하고 여기서 마무리했습니다. 

 

 

최종 스코어 8솔브 22등으로 마무리했습니다. 8솔브 팀의 대부분이 G를 시도하지 않았습니다. 이것이 8솔브와 9솔브를 가르는 분기점이 아니었나 싶습니다. 중간에 잠깐 읽었을 때 확률 얘기가 나와서 귀찮은 계산이 있지 않을까 하고 지레 겁먹고 펴보지 않았는데 생각보다 어렵지 않게 해결가능하다는 것 같았습니다. 본선 전에 업솔빙해볼 법한 문제 같습니다.

A. Frog 1

문제 요약: 다음 칸 또는 그 다음 칸으로 점프할 수 있고, 그때 비용은 높이 차일때 칸 $1$에서 칸 $N$으로 이동하는 최소 비용

풀이:

점프 하나를 간선 하나로 해석하면 dp 점화식 ```dp[i] = min(dp[i-1] + abs(h_{i}-h_{i-1}), dp[i-2] + abs(h_{i}-h_{i-2}))```를 얻을 수 있습니다. top down으로 구현하면 $i-2$가 음수가 될 수 있음에 유의해야 하며 bottom up으로 구현하면 $N$보다 큰 padding을 마련해야 합니다. 

 

B. Frog 2

문제 요약: A번 문제에서 한 번에 점프할 수 있는 거리가 최대 $K$인 문제

풀이:

A와 동일하게 해결하면 됩니다. 업데이트 간선의 개수는 $NK$개입니다.

 

C. Vacation

문제 요약: $i$번째 날에 A,B,C를 하면 얻을 수 있는 행복도는 각각 $a_i, b_i, c_i$이고 이틀 연속으로 같은 행동을 할 수 없을 때 행복도의 최댓값

풀이:

```dp[i][j]```를 $i$번째 날에 $j$번 일을 했을 때의 행복도의 최댓값을 관리하게 하면 ```dp[i]```는 ```dp[i-1]```에만 의존하므로 $i$를 증가시키면서 업데이트하면 됩니다. 업데이트 간선의 개수는 $6(N-1)$개입니다.

슬라이딩 윈도우를 통해 해결할 수도 있습니다.

 

D. Knapsack 1

문제 요약: 비용이 $w$이고 가치가 $v$인 물건을 비용이 $W$ 이하가 되게 고를 때 가치 합의 최대

풀이:

일반적인 0-1 knapsack 문제는 다항 시간 내에 풀 수 없음이 알려져 있습니다. 하지만 $W$의 값은 다항 크기가 아니므로 이를 이용해야 합니다.

```dp[i][j]```를 $1$번 물건부터 $i$번 물건까지 이용해 비용이 $i$인 선택을 할 때 최대 가치라고 하면 비용이 $w$이고 가치가 $v$인 물건을 추가로 선택하면 ```dp[i+1][j+w]```에 ```dp[i][w]+v```인 경우가 추가되는 것과 같습니다. 이를 ```max```로 관리하면 ```dp[N]```에 전체 최적해를 가지게 됩니다. 업데이트 간선의 개수는 최대 $WN$개입니다. 한편 ```dp[i]```는 ```dp[i-1]```에만 의존합니다. 따라서 첫 번째 인덱스를 슬라이딩 윈도우로 관리할 수도 있고, $j$ 인덱스의 업데이트 방향이 증가하는 방향이므로 큰 인덱스부터 관리하면 크기 $W$인 배열 하나만으로 해결할 수 있습니다.

 

E. Knapsack 2

문제 요약: 비용이 $w$이고 가치가 $v$인 물건을 비용이 $W$ 이하가 되게 고를 때 가치 합의 최대

풀이:

D번과 다른 점은 $W$의 상한이 커진 대신 $v$의 하한이 작아졌습니다. 파라메트릭 서치와 같은 테크닉으로 dp 식을 뒤집어 생각하면 D번에서는 어떤 용량을 가지는 가치의 최댓값을 구했다면 여기서는 어떤 가치를 가지는 용량의 최솟값을 관리하면 될 것 같습니다. 

 

같은 방식으로 ```dp[i][j]```를 $i$번 물건까지 사용해 가치가 $j$가 되게 하는 조합의 용량 합 최소값으로 두면 ```dp[i+1][j+v]```에 ```dp[i][j]+w```를 업데이트하는 것과 같고 min 업데이트를 이용해 구할 수 있습니다. ```dp[0][j]```에서 $j$가 0이 아니면 불가능하므로 min의 항등원인 무한대로 초기화해서 구해야 함에 유의해야 합니다. 마찬가지로 업데이트 간선의 개수는 최대 $N\sum v$이고 $i$의 의존성을 이용해 크기 $\sum v$인 배열만으로 해결할 수 있습니다.

 

F. LCS

문제 요약: 두 문자열의 최장 공통부분수열

풀이:

두 문자열의 길이가 각각 $i$,$j$인 접두사의 LCS를 구하는 dp를 구현하면 됩니다. 이는 각각 $i$,$j$번째 문자를 추가하는 경우로, 두 문자가 같으면 ```dp[i][j] = dp[i-1][j-1]+1```, 다르면 ```dp[i][j] = max(dp[i-1][j], dp[i][j-1])```임을 이용하면 격자 모양의 dag를 얻을 수 있습니다. 업데이트 간선의 개수는 최대 $2|s||t|$개 있습니다.

 

G. Longest Path

문제 요약: Unweighted directed acyclic graph에서 가장 긴 경로의 길이

풀이:

dp 식을 바로 써보면 ```dp[v] = max(dp[u]+1: (u,v) \in E)```입니다. 1번 정점에서 출발하는 것이 고정되어 있으므로 $O(M)$에 해결할 수 있습니다. connected component에서 ```E>=V-1```임을 이용하면 $O(N+M)$으로 쓰지 않아도 됨을 알 수 있습니다.

 

H. Grid 1

문제 요약: 장애물이 있는 격자에서 수직인 두 방향만 이용해서 이동할 수 있는 경우의 수

풀이:

이동 가능한 방향으로 간선을 그리면 dag를 쉽게 얻을 수 있습니다. 어떤 칸에 도달하기 위해서 직전에 방문했어야 하는 칸은 많아야 두 개입니다. 직전에 방문했어야 하는 두 칸에 도착하는 경로의 수를 각각 알고 있으면 두 경로 개수의 합이 이 칸에 도착하는 경로의 수입니다. 업데이트 간선의 개수는 최대 $2HW$입니다.

 

I. Coins

문제 요약: 1이 나올 확률이 각각 $p$인 독립시행 결과 1이 나온 횟수마다의 확률

풀이:

확률론적으로 접근하면 확률이 각각 독립이므로 시행을 한 번 더 하는 경우를 각각 곱의 법칙으로 구할 수 있습니다. ```dp[i][j]```를 $i$번 시행까지 마쳤을 때 $j$번의 1이 나올 확률로 두면 확률 $p$인 시행을 추가하는 경우는 ```dp[i+1][j] = dp[i][j-1]*p + dp[i][j]*(1-p)```임을 확률론적으로 얻을 수 있습니다. dp 상태는 ```i>=j```인 경우 뿐이므로 업데이트 간선의 개수는 최대 ${N^{2}}$개입니다.

 

J. Sushi

문제 요약: $N$개의 접시 위에 초밥이 각각 최대 3개 올려져 있고 접시를 균등하게 뽑아 그 위에 초밥을 먹을 때 모두 먹기까지 시도 횟수의 기댓값

풀이:

모든 접시는 동등하므로 $0$,$1$,$2$,$3$개의 초밥이 올려진 접시의 개수를 각각 $a$,$b$,$c$,$d$라고 뭉뚱그려도 됩니다. 이때 $a+b+c+d$가 항상 $N$이므로 이중 세 개의 값만 가지고 현재 초밥의 개수를 명확하게 구현할 수 있습니다. 이제 ```dp[b][c][d]```를 이 상태에서 초밥을 모두 먹기까지 시도 횟수의 기댓값으로 두면 ```dp[b][c][d] = 1 + dp[b][c][d] * a/N + dp[b-1][c][d] * b/N + dp[b+1][c-1][d] * c/N + dp[b][c+1][d-1] * d/N```가 성립해야 한다는 것을 알 수 있습니다. 우변의 ```dp[b][c][d]```를 넘겨 정리하면 ```dp[b][c][d] = (1 + dp[b-1][c][d] * b/N + dp[b+1][c-1][d] * c/N + dp[b][c+1][d-1] * d/N) * N/a```가 되어 dp식을 얻은 것을 알 수 있습니다. acyclic인 것은 ```b+c+d```의 값이 작은 상태, ```b+c+d``` 값이 같고 ```d```값이 작은 상태, ```b+c+d```와 ```d``` 값이 같고 ```c```값이 작은 상태로 부터만 오기 때문에 세 값을 축으로 격자를 그려보면 알 수 있습니다.

업데이트 간선의 개수는 최대 $3(b+c+d)(c+d)d$입니다.

 

K. Stones

문제 요약: $K$개의 돌에서 $a$개의 돌을 지울 수 있을 때 승리하는 플레이어

풀이:

$a$가 양쪽 모두에게 동등하므로 내가 패배하는 상태를 상대에게 주는 것이 내가 승리하는 것과 동치입니다. ```dp[i]```를 남은 돌의 개수가 $i$일 때 승리할 수 있는지 여부를 담은 부울린 dp로 잡으면 ```dp[i] = not and(dp[i-a])```입니다. 업데이트 간선의 개수는 최대 $NK$개입니다.

 

L. Deque

문제 요약: 덱의 앞 또는 뒤에서 제거해 제거한 수 만큼 점수를 얻어 점수 차를 최대로 하는 게임을 할 때 최대 점수차

풀이:

$X-Y$를 최소화 하는 것과 $Y-X$를 최대화 하는 것이 완전히 동일합니다. 또한 $X+Y$가 고정되어 있으므로 $X$를 알면 $Y$를 알 수 있기 때문에 각자 자신의 점수를 최대화 하는 것과 동치임을 알 수 있습니다.

```dp[l][r]```을 $l$번째 원소부터 $r$번째 원소로 게임할 때 얻을 수 있는 $X-Y$의 최댓값으로 두면 ```dp[l][r] = max(a_l - dp[l+1][r], a_r - dp[l][r-1])```임을 확인할 수 있습니다. 하나를 제거한 상태가 되면 더 작은 게임을 선후공이 바뀐 채로 게임하는 것과 같기 때문에 상대가 최대화한 값의 부호를 뒤집어 내가 최대화한 값을 얻을 수 있습니다. 업데이트 간선 개수는 $O(N^2)$개입니다.

 

M. Candies

문제 요약: $N$명의 어린이에게 $K$개의 사탕을 나누어 줄 때 $a$ 이하로 나누어주는 경우의 수

풀이:

$K$의 값이 크지 않기 때문에 $K$를 인덱스로 잡아 dp하려는 생각을 할 수 있습니다. ```dp[i][j]```를 $i$번째 어린이까지 사탕을 나누어 주고 $j$개가 남는 경우의 수라고 하면 ```dp[i][j] = sum(dp[i-1][j+k]: j+k<=K)```가 됩니다. $dp[i]$가 $dp[i-1]$에만 의존하므로 누적 합을 이용한 정적 구간 합을 통해 $O(NK)$에 dp 테이블을 전부 채울 수 있습니다. dp 배열을 재배열하는 경우 점 업데이트 구간 쿼리를 쓰게 되므로 세그먼트 트리를 이용할 수도 있고, 이 경우 $O(NKlgK)$의 시간과 $O(K)$의 공간으로 해결할 수 있습니다.

 

N. Slimes

문제 요약: 이웃한 두 슬라임을 합칠 때 합친 크기 만큼 비용이 들 때 모든 슬라임을 합치는 데 드는 최소 비용

풀이:

이웃한 슬라임만 합칠 수 있으므로 구간을 합치는 것으로 이해해도 됩니다. ```dp[l][r]```을 $l$번째 슬라임부터 $r$번째 슬라임까지 합치는 최소 비용으로 두면 ```dp[l][r]```은 마지막 합치는 시행 직전 상태는 ```l<=i<r```인 $i$를 기준으로 두 구간이 나뉘어 있었어야 합니다. 따라서 ```dp[l][r] = min(dp[l][i] + dp[i+1][r] + sum(l:r): l<=i<r)```가 성립하고, 구간의 길이가 짧은 구간에서 긴 구간 방향으로 업데이트가 일어나므로 dag가 됨을 알 수 있습니다.

 

이때 업데이트 간선의 개수가 $O(N^3)$개이므로 조금 더 최적화가 필요합니다. 구간을 합치는 데 드는 비용이 구간합이기 때문에 ```sum(a:d)>=sum(b:c)```와 ```sum(a:d)+sum(b:c)>=sum(a:c)+sum(b:d)```가 성립하므로 Knuth optimization 또는 Knuth-Yao speedup이라는 테크닉을 이용할 수 있습니다. dp 식의 min 부분은 주어진 값 가운데 하나를 선택하는 함수인데, 구간 마다 선택한 인덱스를 ```opt[l][r]```이라 한다면 ```opt[l][r-1] <= opt[l][r] <= opt[l+1][r]```이 성립하고 dp값과 동시에 구해주면 같은 길이의 dp를 모두 구하는 데 드는 min 탐색 비용 합이 $O(N)$이므로 전체 $O(N^2)$에 해결할 수 있습니다. ```opt``` 값에 관한 부등식의 증명은 어렵기 때문에 생략합니다.

 

edit: $O(N^3)$으로도 해결할 수 있다고 합니다.

 

O. Matching

문제 요약: 남자 또는 여자로 이루어진 이분 그래프에서 완전 매칭의 개수

풀이:

완전 매칭의 개수를 세야 하므로 1번 남자는 어떤 여자와 매칭되어야 합니다. 1번 남자를 어떤 여자와 매칭시키고 나면 2번 남자 또한 다른 어떤 여자와 매칭되어야 합니다. 같은 방식으로 번호가 작은 남자부터 순서대로 여자를 매칭하는 경우의 수를 세줄 수 있습니다. ```dp[i][j]```를 $i$번 남자까지 매칭한 결과 매칭된 여자의 비트마스크 집합의 결과가 $j$인 경우의 수로 두면 $i$의 모든 간선에 대해서 업데이트해줄 수 있습니다. 이때 ```popcount(j) = i```가 성립하므로 $i$ 인덱스는 필요하지 않습니다. popcount가 작은 인덱스부터 큰 인덱스 순서대로 업데이트해주면 popcount가 가장 큰 인덱스에서 완전 매칭의 개수를 얻을 수 있습니다. 업데이트 간선의 개수는 최대 $O(N 2^N)$개입니다.

 

P. Independent Set

문제 요약: 트리에서 독립 집합의 개수

풀이:

임의의 루트를 잡으면 rooted tree를 얻을 수 있습니다. rooted tree에서 독립 집합은 루트를 포함하는 경우와 포함하지 않는 경우로 나눌 수 있습니다. 루트를 포함하는 경우 maximal subtree의 루트는 포함할 수 없음을 이용하면 루트를 포함하는 경우와 포함하지 않는 경우의 독립 집합의 개수를 따로 세 줘야 함을 알 수 있습니다.

```dp[i][0]```을 $i$를 루트로 하는 서브트리에서 루트를 포함하지 않는 독립 집합의 개수, ```dp[i][1]```를 루트를 포함하는 독립 집합의 개수로 두면 ```dp[i][0] = \prod (dp[j][1]+dp[j][0]: (i,j) \in E)```, ```dp[i][1] = \prod (dp[j][0]: (i,j) \in E)```임을 알 수 있습니다. 각 간선마다 dp 업데이트가 두 번씩 일어나므로 업데이트 간선의 개수는 $2N-2$개입니다.

 

Q. Flowers

문제 요약: $(h,a)$ 꼴의 순서쌍의 수열에서 $h$가 단조 증가하는 부분 수열의 $a$ 값의 최댓값

풀이:

$i$번째로 끝나는 부분 수열의 최댓값은 $i$ 이전에 끝나는 부분 수열의 최댓값 뒤에 $i$번째 $a$를 더한 값입니다. 이를 ```dp[i]```라고 두면 ```dp[i] = max(dp[j]: j<i, h[j]<h[i])+a[i]```가 됩니다. 이를 $h$가 작은 $i$부터 순서대로 업데이트해주면 두 번째 조건을 자동으로 만족하게 됩니다. $i$보다 작은 구간의 최댓값을 구하는 것은 세그먼트 트리를 이용해 $O(lgN)$에 구할 수 있기 때문에 총 $O(NlgN)$에 모든 dp 값을 구할 수 있습니다. $h$가 가장 작은 인덱스의 경우 0번째에 $h$와 $a$가 모두 0인 꽃을 먼저 선택했다고 봐도 무방하기 때문에 예외 처리 없이 해결할 수 있습니다.

 

R. Walk

문제 요약: Directed graph에서 길이가 $K$인 서로 다른 walk의 개수

풀이:

길이가 $a+b$인 walk는 길이가 $a$인 walk와 $b$인 walk로 분리할 수 있고 그 방법이 유일합니다. 반대로 한 시작점과 반대쪽의 종점이 같은 두 walk를 합쳐서 긴 walk를 만들 수 있고 이 방법도 유일합니다. 따라서 y에서 시작해 길이가 $a$인 walk의 집합 $A$, y에서 출발해 길이가 $b$인 walk의 집합 $B$, y를 포함하는 길이가 $a+b$인 walk의 집합 $C$에 대해 $A\times B \cong C$가 성립합니다. $A$의 출발점과 $B$의 도착점 또한 명시해 주면 adjacent matrix의 행렬 곱과 같음을 알 수 있습니다. adjacent matrix의 $K$제곱을 binary lifting으로 구한 뒤 모든 항의 합을 구하면 $O(N^3 lgK)$ 시간에 답을 얻을 수 있습니다.

 

S. Digit Sum

문제 요약: $K$보다 작은 양의 정수 중 자릿수의 합이 $D$의 배수인 수의 개수

풀이:

$K$가 크기 때문에 산술적으로 접근하기 곤란합니다. leading zero를 허용하면 $K$와 같은 자릿수의 정수를 많이 관찰해야 한다는 생각을 할 수 있습니다. 이때 $K$와 leading digit이 같은 수는 계산하기 힘들지만 적어도 하나가 다르면 그 아랫 자리는 자유롭게 선택할 수 있음을 확인할 수 있습니다. ```cnt[i]```를 $K$와 $i$ 길이의 prefix가 같은 정수를 $D$로 나눈 나머지가 0인 정수의 갯수로 두면 ```cnt[i]```는 $i+1$번째 자리부터 자릿수 합이 ```-sum[K[0:i]]```인 원소의 개수와 같습니다. $i+1$번째 자릿수는 $K$보다 작은 경우만 계산하면 됩니다. $K$와 같은 경우는 ```cnt[i+1]```에서 계산할 것이기 때문에 중복 계산을 피하기 위해 $i+1$번째 자릿수가 $K[i+1]$보다 작은 경우만 생각합니다. 그러면 ```cnt[i]```를 ```cnt[i][j]```꼴로 나눠서 $K$와 길이 $i$의 prefix가 같은 정수 중에 $i+1$번째 자릿수가 $j$인 정수 중 $D$로 나눈 나머지가 0인 개수로 두면 ```cnt[i][j]```는 $i+2$번째 자리부터 자릿수 합이 ```-sum[K[0:i]]-j```인 원소의 갯수와 같습니다. 여기서는 모든 자리의 자릿값을 0부터 9까지 완전히 자유롭게 선택할 수 있습니다. leading zero를 포함하여 $x$자리인 정수 중에서 $D$로 나눈 나머지가 $y$인 정수의 개수를 ```dp[x][y]```로 두면 dp값은 쉽게 구할 수 있습니다. 이 dp의 업데이트 간선의 개수는 대략 $10lgK$개입니다. 이제 모든 ```cnt[i][j]```의 값을 구하면 총 $O(DlgK)$개의 값을 각각 상수시간에 구할 수 있습니다.

 

T. Permutation

문제 요약: 주어진 부등식을 만족하는 서로 다른 순열의 갯수

풀이:

부등식을 만족하면 실제 수의 배열은 어떻게 주어지더라도 무관함을 이용할 수 있습니다. 앞에서부터 $i$개의 수를 채운 결과 $i$번째 값 보다 작은 원소의 개수가 $j$개가 되는 순열의 개수를 ```dp[i][j]```라고 둡니다. 그리고 ```s[i]```에 따라 두 가지 경우의 수로 나뉩니다.

 

```s[i]```가 <인 경우라면 이전까지 구한 순열의 마지막 원소보다 큰 원소를 취해야 합니다. ```dp[i][j]```에서 마지막 원소보다 큰 원소는 ```i-dp[i][j]-1```개 있습니다. 이중 하나를 고른 뒤 이보다 크거나 같은 이전에 등장한 모든 원소에 1을 더해주는 방법으로 $i+1$번째 원소를 특정할 수 있습니다. ```dp[i+1][k] += dp[i][j]: k>j```와 같은 구간 업데이트를 진행할 수 있습니다.

반대로 ```s[i]```가 >인 경우라면 1부터 $j$까지 고를 수 있으므로 ```dp[i+1][k] += dp[i][j]: k<=j```와 같은 구간 업데이트를 진행할 수 있습니다. ```dp[i]```가 ```dp[i-1]```에만 의존하므로 $i$를 고정해 두고 구간 업데이트 쿼리를 이용할 수 있고, imos 등의 방법으로 $i$마다 $O(N)$에 해결할 수 있습니다. 반대로 ```dp[i][k] = \sum (dp[i-1][k])``` 꼴로 수정해 구간 합 쿼리를 이용할 수도 있습니다. 세그먼트 트리를 이용할 수도 있습니다.

 

U. Grouping

문제 요약: $N$개의 원소를 파티션 지어 같은 부분에 들어간 쌍마다 점수를 얻을 때 점수의 최댓값

풀이:

모든 원소를 분리할 수 있으므로 답은 0 이상입니다. ```dp[i]```를 비트마스크가 i인 부분집합을 파티션 지을 때 얻을 수 있는 점수의 최댓값으로 우선 정의하고 싶어집니다. top down이나 bottom up이나 비트를 뒤집으면 같은 모양이기 때문에 업데이트 간선의 개수는 같습니다. bottom up 관점에서 볼 때 ```dp[i] = max(dp[i-j] + cost(j): j \in i)```임을 확인할 수 있습니다. ```cost(j)```는 비트마스크가 $j$인 부분집합의 점수입니다. 이를 나이브로 구현하면 맞을 수 있습니다.

업데이트 간선의 개수는 모든 부분집합의 모든 부분집합의 원소 개수 합인데, 이항정리를 이용해서 업데이트 간선의 개수를 구할 수 있습니다. 비트마스크 $i$의 부분집합의 개수는 $2^{popcount(i)}$입니다. popcount가 같은 $i$끼리 모으면 총 업데이트 간선의 개수는 $\sum _ {k=0} ^{k=N} 2^{N} nCr(N,k)$인데 이항정리에 의해서 $(2+1)^N$과 같으므로 총 $3^N$개의 업데이트 간선이 있습니다.

cost를 나이브로 구하면 매 업데이트마다 최대 $N^2$번 더 연산하게 되므로 cost를 전처리해야할 수 있습니다.

 

V. Subtree

문제 요약: Unrooted tree에서 특정 정점을 포함한 connected subgraph의 개수

풀이:

rooted tree에서 subtree의 개수를 세는 것은 ```dp[v] = \prod (dp[u]: (u,v) \in E) + 1```으로 상대적으로 쉽습니다. 만약 dp가 체의 원소라면 간선을 쉽게 끊을 수 있습니다. root 정점 r, r의 자식 v에 대해 ```dp[r] = (dp[r]-1)/(dp[v]) + 1```, ```dp[v] = (dp[v]-1)*dp[r] +1```을 이용하면 루트를 r에서 인접한 v 정점으로 바꾼 경우의 dp 배열을 $O(1)$에 얻을 수 있습니다. 이 과정을 통해 전체 트리를 dfs하면 각 정점이 루트일 때 루트의 dp 값을 $O(N)$에 구할 수 있습니다.

체의 원소가 아니기 때문에 나누기 연산을 사용할 수는 없지만 나누는 행위가 r의 모든 자식 정점 중 v를 제외한 모든 정점을 합치는 것과 같음을 이용할 수 있습니다. 모든 자식 정점 v마다 v가 아닌 정점의 dp 값을 모으는 연산을 하게 되면 $\sum deg(v)^2$만큼 연산이 필요해 비용이 가장 큰 정점에서 $O(N^2)$보다 작을 수 없습니다. 대신 v의 접두/접미 누적합을 이용하면 $O(deg(r))$의 전처리를 통해 각 v마다 v를 제외한 dp값을 $O(1)$에 얻을 수 있습니다. 각 정점이 루트가 될 때마다 $O(deg(r))$의 전처리가 필요하므로 총 전처리 비용은 $O(\sum deg(r)) = O(N)$이 됩니다.

 

W. Intervals

문제 요약: 몇 개의 정수를 선택해 주어진 구간이 선택한 정수를 포함하는 경우 점수를 획득할 때 점수의 최댓값

풀이:

```dp[i]```를 선택한 정수 중 최댓값이 $i$일 때 점수 최댓값으로 두고 작은 수부터 순서대로 선택하고 싶어집니다. $i$를 선택하기 직전에 선택한 가장 큰 정수가 $j$라면 ```dp[i] - dp[j]``` 값은 $i$를 포함하면서 $j$를 포함하지 않는 구간의 가중치 합이 됩니다. 이를 나이브로 구하면 $O(M)$의 비용이 들고 각 $j$마다 구하면 총 $O(N^2M)$만에 답을 얻을 수 있습니다. $i$보다 작은 범위와 겹치는 구간의 가중치 합 등을 누적합으로 하는 차분 배열은 올바른 dp값의 차를 구할 수 없습니다. 

 

dp 식을 써보면 ```dp[i] = max(dp[j] - cost(i,j): j<i) + sum(i)```로 정리할 수 있습니다. 이때 ```cost(i,j)```는 $i$와 $j$를 동시에 포함하는 구간의 가중치 합, ```sum(i)```는 $i$를 포함하는 구간의 가중치 합입니다. 가중치가 $A$인 ```cost(i,j)```에 포함되는 구간 ```[L:R]```은 ```cost(i,L:R)```에 $-A$만큼 영향을 줍니다. dp값을 세그먼트 트리로 관리하면 해당 구간이 $i$를 포함하는 동안 $[L:R]$에 $-A$만큼 기여합니다. 이와 독립적으로 ```sum(i)```에 포함되는 구간은 $j$에 관계없이 영향을 줍니다. 따라서 $j$를 전체 범위로 확장해 보면 ```sum(i)```에 포함되는 모든 구간 $[L:R]$은 $[L:R]$이 아닌 범위에서만 $A$만큼 영향을 주는 것으로 볼 수 있습니다. 

 

실제로 구현을 구체화해 보면 $i$를 작은 것부터 순회하면서 $i$를 포함하는 모든 구간만을 동시에 관리합니다. 이때 세그먼트 트리를 이용해 해당 구간이 아닌 범위의 값을 구간의 가중치 만큼 증가시킵니다. 이때 $i$보다 작은 인덱스에서 ```dp[j] - segtree[j]```값의 최댓값이 ```dp[i]```값이 됩니다. 세그먼트 트리의 각 인덱스마다 ```dp[i]```값을 증가시켜놓으면 한 번의 세그먼트 트리 쿼리만으로 dp값을 구할 수 있습니다. 각 구간이 세그먼트 트리에 한 번 들어갔다 나오고 각 인덱스마다 세그먼트 트리의 구간 쿼리를 한 번 수행하므로 총 $O((N+M)lgN)$에 답을 구할 수 있습니다. 일본에서 starry sky tree로 알려진 유사 세그먼트 트리로 해결할 수도 있습니다.

 

X. Tower

문제 요약: 무게 $w$인 블럭 위에 무게 합이 최대 $s$인 블럭까지 쌓을 수 있을 때 쌓인 블럭의 가중치 합 최대

풀이:

블럭을 쌓는 순서가 중요하므로 블럭을 쌓는 순서를 정하는 것이 중요합니다. 각 블럭의 무게를 $W[i]$, 쌓을 수 있는 한계를 $S[i]$, 가중치를 $V[i]$라고 하고 쌓을 수 있는 한계를 용량이라고 부르면 좋아 보입니다. 용량 $s$ 위에 블럭 $i$, $j$가 아래에서부터 순서대로 쌓여있을 때 가장 꼭대기에 추가로 쌓을 수 있는 용량의 크기는 ```min(s-W[i]-W[j],S[i]-W[j], S[j])```입니다. 반대로 블럭 $j$, $i$가 순서대로 쌓여있다면 ```min(s-W[i]-W[j],S[j]-W[i], S[i])```입니다. 이때 $i$를 위에 쌓는 것이 더 이득이려면 ```min(s-W[i]-W[j],S[i]-W[j], S[j]) <= min(s-W[i]-W[j],S[j]-W[i], S[i])```를 만족해야 합니다. 가장 첫 항은 항상 같기 때문에 우변의 최솟값이 ```s-W[i]-W[j]```인 경우는 고려하지 않아도 됩니다. $i$와 $j$가 대칭식이기 때문에 좌변의 최솟값인 경우도 동일합니다. 따라서 ```min(S[i]-W[j], S[j]) <= min(S[j]-W[i], S[i])```가 성립하면 $i$를 위에 쌓는 것이 항상 유리합니다. 증명 없이 이 식을 기준으로 블럭을 정렬해 dp식을 세우면 맞을 수 있습니다.

 

식을 약간 변형하면 ```min(S[i]+W[i], S[j]+W[j]+W[i]) <=  min(S[j]+W[j], S[i]+W[j]+W[i])```처럼 쓸 수 있습니다. 우변의 최솟값이 ```S[i]+W[j]+W[i]```라면 우변은 ```S[i]+W[i]```보다 클 수 없기 때문에 항상 부등식이 성립합니다. 따라서 이 경우도 제외할 수 있습니다. 따라서 ```S[i]+W[i]<=S[j]+W[j]```인 경우에 $i$를 위에 쌓는 것이 유리하므로 $S+W$를 기준으로 큰 블럭부터 쌓는 것이 유리합니다.

 

```S[i]+W[i]<=S[j]+W[j]```이고 $i$ 위에 $j$를 쌓을 수 있다면 ```min(s-W[i],S[i])<=W[j]```를 만족합니다. 식을 변형해서 ```min(s-W[j],S[i]+W[i]-W[j])<=W[i]```를 얻습니다. 만약 둘 중 더 작은 값이 $s-W[j]$라면 문제 없이 $j$ 위에 $i$를 쌓을 수 있습니다. 아니라면 ```S[i]+W[i]-W[j] <= s-W[j] <= W[i]```를 얻어 ```S[i]<=W[j]```임을 알 수 있습니다. ```min(s-W[j],S[j]) <= min(W[i],S[j]) <= W[i]```이므로 이 경우도 $j$ 위에 $i$를 쌓을 수 있습니다. 따라서 $S+W$가 큰 블럭부터 순서대로 쌓는 dp를 구성하면 해결할 수 있습니다.

 

이제 마지막으로 ```dp[i][j]```를 $i$번째로 $S+W$가 작은 블럭까지 쌓았을 때 여유 용량이 $j$일 때 가중치 합 최대로 두면 0-1 배낭 문제로 해결할 수 있습니다. 정렬에 $O(NlgN)$의 시간이 필요하고 업데이트 간선의 개수는 최대 $O(N \sum s)$개입니다.

 

Y. Grid 2

문제 요약: 장애물이 있는 격자에서 수직인 두 방향만 이용해서 이동할 수 있는 경우의 수

풀이:

문제 제한이 크므로 모든 경우의 수를 다 구할 수 없습니다. $N$개의 장애물을 장애물이 아닌 도착점으로 생각합니다. ```dp[i]```를 $i$번째 장애물에 다른 장애물을 거치지 않고 도달할 수 있는 경로의 수로 둡니다. ```dp[i]```는 다른 장애물에 상관 없이 $i$번째 장애물에 도달하는 경우의 수에서 다른 장애물을 거쳐 도착하는 경우의 수를 뺀 값입니다. 또한 ```dp[i]```는 $i$가 처음 만난 장애물이고 그 자리에서 멈춘 경우의 수로 이해할 수도 있습니다. 따라서 ```dp[i] = nCr(r[i]+c[i]-2,r[i]-1) - \sum (dp[j] * nCr(r[i]+c[i]-r[j]-c[j],r[i]-r[j])```가 성립합니다. $j$의 범위에 따라 이항계수를 구할 때 문제가 생길 수 있음에 주의해야합니다.

$(H,W)$에 도달하는 경우의 수는 $(H,W)$에 장애물을 하나 더 추가로 놓고 이 장애물에 처음으로 도달하는 경우의 수를 세는 것과 같습니다. 업데이트 간선의 개수는 최대 $O(N^2)$개이고 이항계수를 상수시간에 구할 수 있도록 $O(H+W)$에 전처리해둘 수 있습니다.

 

Z. Frog 3

문제 요약: 위치가 증가하는 방향으로 점프하는 비용이 높이 차이의 제곱일 때 $1$번 칸에서 $N$번 칸으로 이동하는 최소 비용

풀이:

```dp[i]```를 $i$번 칸으로 이동하는 데 드는 최소 비용이라고 두면 ```dp[i] = min(dp[j] + (h_i-h_j)^2 + C: j<i)```입니다. 식을 변형하면 ```dp[i] = min(-2h_jh_i + dp[j] + h_j^2) + C+h_i^2```이 됩니다. 이는 일차함수의 모임에 대입한 값의 최솟값을 구하는 쿼리로 해결할 수 있습니다. convex hull trick 또는 Li-Chao tree를 통해 ```dp[i]```값을 구한 뒤 직선 ```-2h_i x + dp[i] + h_i^2```을 추가하는 것으로 해결할 수 있습니다. $h_i$가 증가하므로 선형 CHT를 수행할 수도 있고, Li-Chao tree를 통해 $O(Nlgh_N)$에 해결할 수도 있습니다.

'Problem solving' 카테고리의 다른 글

UCPC 2026 예선 후기  (0) 2026.07.02
Good Bye, BOJ 2025! 후기  (0) 2026.02.23
ICPC 2025 Seoul Regional Competition 후기  (1) 2026.01.08
ICPC 2025 Seoul Regional Nationwide Copetition 후기  (0) 2025.12.30
BOJ 18457 Knowledge  (2) 2025.08.02

Good Bye, BOJ 2025!의 온사이트가 공지되었습니다. 선정 기준이 공기되면서 당연히 많은 관심이 기준에 쏠리게 되었습니다.

물론 저는 장소가 더 중요했습니다. 공지된 장소는 서울 강서였고, 신청해서 되면 가고 아니면 집에서 적당히 비비다 말자는 생각이었습니다. 아무래도 1단계 조건이 되긴 하지만 보통 저는 운이 좋지 않은 편이었기 때문에 크게 문제없으리라 생각했습니다. 다이아 난이도 문제는 아직 100문제도 제대로 풀지 못했거니와 플래티넘 문제도 열심히 푼 기억도 없었기 때문에 아무래도 가게 된다면 1단계일 것이고 그렇지 않으면 못가리라 생각했습니다.

그런데 예상치 못하게 4단계에서 선정되었습니다. 아무래도 1,2,3단계 선정된 분들또한 골드를 많이 풀었기 때문에 컷이 내려온게 아닌가 싶습니다. 골드 1000문제 풀기 챌린지를 진행하는 중이기도 했고 특히 저 즈음해서 문제수가 많이 올라왔기 때문이지 않나 싶습니다. 

 

뭐 어떻게 잘 도착했습니다. 사실 저 사진찍은 시점에서 조금 더 헤메긴 했는데 아무 건물이나 들어가서 아무나 붙잡고 건물 위치를 물어보니 친절하게 답해주셔서 쉽게 찾아갈 수 있었습니다. 아무래도 LG 직원분처럼 보였습니다. 진한 서울 말씨에서 서울에 왔다는 체감이 됐습니다.

 

로비에 도착해서부터는 사진이 없습니다. 아무래도 엄청 많은 고수의 분위기에 압도돼서 사진을 찍어야 한다는 생각을 못했던 것 같습니다. 로비에는 @man_of_learning님와 @stonejjun03님이 안내를 봐주셨습니다. @stonejjun03님은 제 이름을 보시고 바로 알아차리신 눈치였지만 @man_of_learning님은 그렇지 못했던 것 같습니다. 심지어 @jhnah917이라고 써있는 명찰을 메고 계셔서 저도 못 알아봤습니다.

 

저는 ICPC 팀노트를 그대로 챙겨갔는데, 핸들이 인쇄되어있어야 한다는 조건을 보지 못했습니다.

사실 이런 깃허브 페이지가 있다는 것도 지금 확인했습니다.

다행히 운영진측의 배려로 현장에서 수기로 작성하는 것을 허용해주셨고, 뒤에 줄서계시던 @nflight11님의 펜을 빌려 핸들을 기입하고 접수를 오랫동안 하고 대회장으로 올라갔습니다.

 

대회가 시작되기 전에는 서로 대화하는 가벼운 시간이 있었습니다. 주최측에서 준비해주신 시간은 아니고 여기 추첨될 만큼 문제를 푼 사람들이면 어느정도 서로 친분이 있는 경우가 종종 있어서 자연스럽게 분위기가 생긴 듯합니다. @nflight11님께 펜을 돌려드리고 여러 사람들에게 아는 척을 했던 것 같습니다. 얘기나눈 분들을 다 하나씩 얘기하고 싶지만 다 기억나지 않는 관계로 공평하게 모두 얘기하지 않겠습니다. 

 

아마 @ryute님이었던 것으로 기억합니다. 대회 시작 직전 앞에서 통제해 주셔서 대회를 시작하게 되었습니다.

결과가 썩 좋지 않습니다. 참가자 평균 실력이 높다 보니 하위권에 머물렀습니다. 

A (00:16): 간단하게 10의 거듭제곱으로 나눈 몫과 나머지의 곱을 구하는 구현이 제일 어려웠습니다. 아마 파이썬을 썼으면 금방 구현할 수 있지 않았나 싶습니다. 아무래도 이걸 빨리 짰으면 50등으로 올라갔을 지 몰랐을 것 같습니다. N이 충분히 작기 때문에 T+N의 구현을 하는 것이 맹점인 문제였습니다.

 

B (-4): 딱 보자마자 harmonic series가 생각났습니다. 그래서 바로 구현에 들어갔습니다. 제곱처럼 짜면 로그로 떨어지고 LlgN에 해결된다는 생각으로 단순 구현을 했는데 쉽지 않았습니다. 요지는 장면을 직접 세면 안되고 프레임에 장면이 몇 개 들어가는지를 빠르게 알아 프레임의 개수를 트래킹하는 것이었습니다. 바로 구현이 안되겠다 싶어 프레임 수를 기준으로 장면 개수를 트래킹하는 파라메트릭 서치로 방향을 틀었는데, 이것이 패착이 된 것 같습니다. 물론 답의 범위가 3천만 미만임을 확인했지만 거기서 마무리했습니다.

 

C: (02:20): 오래 고민을 했습니다. 결국 PBA를 했는데 왠지 optimal은 아니지만 충분히 클 것이라고 판단해 구현했습니다. 끝나고 @kwoncycle님에게 해설을 들었는데 명쾌한 증명이 있었습니다. 

 

D: (01:36, +3): 보자마자 풀이가 보였습니다. 결과적으로 반쪽짜리 풀이였지만 풀이의 핵심 부분이긴 했습니다. dfs 순서대로 제거하면 된다고 생각했지만 WA를 받았습니다. 제 기억으로는 3개의 패널티가 모두 같은 코드였습니다. 1솔브 시점이었기 때문에 조급한 나머지 패널티 신경쓰지 않고 무작정 제출했던 것 같습니다. 아무 생각 없이 정렬해볼까 생각한 순간 풀이가 짜맞춰졌습니다. 20이 맥거핀이라고 생각했지만 아니었다는 것을 깨닫고 바로 AC를 받았습니다.

 

좀 아쉬운 결과를 받았습니다. B를 업솔빙하겠다고 생각했지만 거의 두달동안 손도 안대봤습니다. 아무래도 반성해야 할듯 합니다. 

 

스코어보드 공개는 @jhnah917님이 해주셨습니다. 제 핸들을 보고 어떻게 읽는지 물어보셨던 것 같은데 제가 딴짓하느라 못들어서 대답을 못해드렸습니다. 그래서 제 핸들은 [큐어쩌고]라고 읽는게 되어버렸습니다. 제가 어디 앉아있는지는 어떻게 아셨을까요.

 

스코어보드 공개가 끝나고 @nflight11님과 @hibye1217님이 저녁 파티를 모았습니다. 당초 공지되었던 19:00보다 한 시간 이상 일찍 마무리되어 저녁 파티에 합류하게 되었습니다. 총 9명이 이동했던 것 같습니다. @queued_q님이 가져오신 퍼즐을 구경하면서 식사를 기다렸습니다. 

 

식사를 마치고 다들 버스타고 돌아간다, 지하철타고 돌아간다 등 얘기를 들으니 참 마음이 착잡해졌습니다. 아무래도 서울로 이사가야할 것 같습니다.

https://blueqv.tistory.com/22

 

ICPC 2025 Seoul Regional Nationwide Copetition 후기

ICPC 2025 서울 리저널 인터넷 예선이 끝났습니다. 끝난지는 한참 됐는데 그래도 해 바뀌기 전에 후기는 써야할 것 같아 급히 작성합니다. Just Reboot JungSangHwa라는 팀명으로 출전했습니다. 이상한 뜻

blueqv.tistory.com

 

 

경희대학교 Memoria가 목요일에 부산에 일찍 도착했습니다. Memoria의 987, jwpassion1과 kolorvxl과 저녁을 먹었습니다. 조금 늦은 시간이었지만 잘 먹고 잘 돌아갔습니다.

문제가 여기서 발생했습니다. 구글 맵스가 이미 없어진 버스 노선을 가르쳐 줘서 자정이 넘은 시간에 30분동안 아무 것도 없는 버스 정류장에서 기다리다가 이상함을 눈치채고 다른 이동수단을 찾기 시작했습니다. 마침 휴대폰 배터리가 다 돼 다른 뾰족한 수가 없었습니다. 결국 20여km를 걸어가기로 단행하고 새벽 3시가 되어서야 학교 인근에 도착할 수 있었습니다. 다음날 예비소집에 늦지 않기 위해 별 말 할 새 없이 바로 취침해야 했습니다.

 

예비소집때는 별 문제 없이 잘 도착했습니다. 가장 먼저 에스컬레이터 앞에서 안내중인 PULSE 졸업생 선배(선배는아니고) M00n과 인사를 하고 대회장 앞에서 노숙하고 있는 팀원과 만나 등록해 들어갔습니다. 

연습 세션에는 큰 이슈는 없었습니다. 연습 세션에는 24B가 연습 문제로 나왔는데, kolorvxl이 연습삼아 풀었습니다. 작년 대회때 인상깊게 푼 문제였기 때문에 기억하고있습니다.

 

예비소집이 끝나고 서울대학교 TempleRun팀, DidYouCheckEERTREE팀의 meow_love, 카이스트 Entropi 팀, Rock kokiri Paper 팀의 cywohoy와 저녁을 먹었습니다. 제가 앉은 테이블에는 저와 서울대학교 4명이 앉았는데, 서울 리저널이 열리는 지역의 지역명을 딴 대학이므로 서울대학교라는 드립을 쳤던 기억이 있습니다. 서로 잘 알고 지내는 사이여서 테이블에서 늘 있는 WWE만 오갔던 것 같습니다. 이 날도 이슈가 있었습니다. 벡스코 인근 섬식버스정류장에 익숙하지 않았던 나머지 6729skl과 같이 반대로 가는 버스를 타고 말았습니다. 빠르게 눈치채 별 문제없이 반대 방향으로 갈아탈 수 있었습니다.

 

기본 전략은 천천히 따라가자 였습니다. 초반 패널티가 중요한 템포는 아니라고 생각했기 때문에 조금 더 천천히 봐도 된다고 생각했고, 빨리 풀지 않아도 솔브 수가 높으면 그만한 보상이 될만한 셋이라고 생각했습니다. 초반 10분동안은 스코어보드를 천천히 보기만 하면서 문제 분석을 시도했습니다.

 

문제 순서대로 문제지를 배분한 것이 실책이었던 것 같습니다. 결과적으로 큰 이슈가 되지는 않았으나 문제를 셔플해서 분배하는 것도 전략이 될 수 있겠다 싶었습니다. 초반 빠른 타이밍이 6729skl이 L의 풀이를 가지고 왔습니다. 저와 풀이를 함께 검토하는 동안 kolorvxl이 스코어보드를 보고 M번을 읽기 시작했고, M의 풀이가 훨씬 간단해 먼저 구현했습니다.

이후 L을 두 번이나 틀렸습니다. y와 l,r의 제한 차이가 있음을 놓쳤던 것을 빠르게 확인하고 구현을 수정해 맞았습니다.

 

2솔브 30위로 올라간 뒤 스코어보드를 확인 후 바로 G를 확인했습니다. G의 부담스러운 삽화를 뒤로 하고 아마 30분 안되어 풀이를 금방 찾았던 것 같습니다. 풀이를 정리하는 동안 kolorvxl이 C를 읽고 구현했던 것 같습니다. 결국 둘 다 WA를 받았습니다. G를 구현하는 동안 C를 디버깅에 성공해 3솔브로 올라가던 중에 G에서 추가로 5패널티를 받았습니다.

6729skl이 코드를 읽고 디버깅을 하면서 제가 D,E를 읽었습니다. 뭣도 안되는 전략이라고 생각하긴 하나 G를 계속 보면 터널 비전에 빠져버릴 것만 같아서 눈을 좀 돌렸습니다. 결국 C에서 AC를 받은 118분부터 E를 맞은 179분까지 G에 저와 6729skl이 묶여 있었고 kolorvxl도 머리식히느라 잠시 쉬던 탓에 팀 전체가 물려버리는 사태가 벌어졌습니다. 이 시점까지 상당히 절망적인 결과라 할 수 있을 것 같습니다.

 

kolorvxl에게 E를 설명해 주고 다시 G로 돌아왔습니다. 스코어보드를 보나 체감을 보나 분명 G는 구현에 말려 못 풀고 있던 것이었고, 다른 문제를 풀어도 결국 G를 풀어야한다는 압박감이 심해졌습니다. kolorvxl이 E를 해결해 구현하는 동안 저와 6729skl는 코드를 다시 뒤집어 엎는 최후의 선택을 하게 되고, 6729skl이 처음부터 코드를 작성해 AC를 받을 수 있었습니다. 최종 해설때 오버플로에 유의해서 작성해야할 수 있었다는 말에 6729skl이 거의 몇시간동안 보고 있던 의미는 있었다고 했지만 여전히 두 사람을 묶어둔 잘못이 크다고 생각합니다.

 

이제 남은 문제는 D를 풀어내고 I를 해결하는 것이었습니다. D는 제가 보던 문제였기 때문에 제가 계속 읽고, I를 kolorvxl이 읽었습니다. G번의 여파로 더이상 코드를 구현할 수 없는 상황이 되어 kolorvxl에게 풀이를 대충 설명했습니다. D를 kolorvxl이 어떻게든 차력쇼로 구현했던 것 같습니다. 그리고 나온 말이 I가 개레전드구현비빔밥이라는 것이었습니다. I를 구현하는 동안 더 뭔가 풀기는 어려울거라 생각하고 낮은 7솔브 마무리를 예상했습니다. 

 

그런데 대회장 분위기가 심상치 않습니다. K가 생각보다 할만해보였고, 웅성웅성하면서 불도저가 들렸습니다. 6729skl이 UCPC2025의 매내처를 보는 듯했다고 했습니다. K를 읽자 마자 각도 정렬 후 정렬 순서에 따른 스위핑이 필요할 것이라고 바로 짐작하고, 불도저를 비벼서 푸는 풀이를 만들어냈습니다. I 구현을 끝낸 kolorvxl을 붙잡고 구현하라고 주장했습니다. 이미 G번에서 큰 패널티를 받았기 때문에 8솔 최하위인 Endgame보다 이미 패널티 수치가 높았고, 8솔브로 올라가더라도 수상은 어려운 상황이었습니다. kolorvxl이 네 포인터 돌리기라는 똑붙 구현을 했고, 제가 외우고 다니는 각도 정렬 구현으로 정렬에 막히지 않아 294분에 겨우 K를 풀어내는 데 성공해 23위로 올라갔습니다. 이후 CPG와 2beatdown에 역전을 허용해 25위로 마무리했습니다.

 

단연 이번 셋에서 가장 잘못한 점이라면 G를 구현하는 데 두 명이나 스턴 걸어놓고 한 시간 넘게 아무 것도 못한 것이라고 할 수 있습니다. 물론 결과적으로 패널티 관리를 잘 했으면 20등 이내에 비벼지기도 하거니와 H도 어떻게 비벼봄직 했겠다마는 결과를 떠나서 퍼포먼스가 가장 안좋았던 타이밍이기도 해 많은 아쉬움이 있습니다. 

 

대회가 끝나고 전북대학교 2 3 5 8 14의 iluem100, 전남대학교 PIMM팀, kolorvxl이 저녁을 먹었습니다. 대회가 마무리 되고 대회의 여운, 아쉬움으로 인한 스턴, 약간의 업무얘기를 이유로 거의 마지막까지 남아있었던 것 같은데 똑같은 상황의 iluem100과 저녁 파티를 만들게 되었습니다. 모든 파티 멤버는 iluem100이 모아왔습니다. 앳코더 쳐야한다고 먼저 간 kolorvxl을 제외하고 가장 가까운 저는 그렇다 치더라도 마지막까지 남아있던 0do, iluem100은 잘 돌아갔는 지도 잘 모르겠습니다. 2 3 5 8 14는 마닐라도 무사히 다녀왔으니 잘 들어간 셈 치기로 했습니다.

 

어쩌다보니 아주 운이 좋게도 69시드로 APAC에 진출하게 되었습니다. Endgame이 마닐라 리저널을 우승한다면 같은 경우의 수를 내심 신경쓰고 있었는데 그런 복잡한 계산 없이 바로 가게 될줄은 생각하지도 못한 결과였습니다.

아무래도 다시는 없을 APAC 기회라고 생각하니 심기일전하게되는 것 같습니다. 아무래도 이번 팀이 올해도 저만 남게 될 것 같고 다시 없을 전력이라 생각됩니다. 단순히 아챔 가봤다도르로 끝나지만 않았으면 좋겠습니다.

'Problem solving' 카테고리의 다른 글

Atcoder Educational DP Contest 풀이 요약  (0) 2026.06.08
Good Bye, BOJ 2025! 후기  (0) 2026.02.23
ICPC 2025 Seoul Regional Nationwide Copetition 후기  (0) 2025.12.30
BOJ 18457 Knowledge  (2) 2025.08.02
ICPC 2024 Regional Seoul 후기  (2) 2025.07.17

ICPC 2025 서울 리저널 인터넷 예선이 끝났습니다. 끝난지는 한참 됐는데 그래도 해 바뀌기 전에 후기는 써야할 것 같아 급히 작성합니다.

 

Just Reboot JungSangHwa라는 팀명으로 출전했습니다. 이상한 뜻이 있는게 아니고 ICPC 2024의 팀 JungSangHwa를 리부트해서 새롭게 멤버를 짜왔다는 의미입니다. 

 

qvixnh22: 2024 JungSangHwa 팀에서 유일하게 남은 팀원입니다. 논증기하, 그래프, 직관에서 증명으로 넘어가기에 상대적으로 강하고 구현에 아주 약합니다. 이 부분은 본선에서 아주 크게 작용합니다.

6729skl: 2024 LeetTtukBang 팀 멤버입니다. LeetTtukBang의 mbae059와 Yera_가 졸업을 하는 바람에 혼자 남겨졌습니다. 팀에서 개발 경력이 가장 길기 때문에 디버깅에 강합니다. 하지만 이 부분도 본선에서 크게 작용합니다.

kolorvxl: 25학번 신입생(였던 것)입니다. 코드포스 레이팅이 높은 만큼 구현 실력이 팀에서 가장 좋습니다. 로직 이해가 어려운 편인 문제의 구현을 전적으로 맡았습니다.

 

대회 직전까지 본선 진출에 대해서는 아무 걱정이 없었습니다. 부산대학교 팀 역사상 가장 강력하다고 추정되는 수준의 팀이 나왔다싶기도 했고, 학교 1등으로 단독 진출을 노리기에 다른 팀들에 비해서 실력 차이가 충분히 난다고 판단했기 때문입니다.

 

천천히 시작했습니다. 2024 예선때와 마찬가지로 교내 전체 참가자가 강의실에 모여서 컨테스트를 진행했고, 또 마찬가지로 문제지가 빠르게 도착하지 않았기 때문에 문제지를 모니터로 가장 먼저 확인했습니다.

 

문제지를 순서대로 나눠 가지고 문제를 읽었습니다. 제가 A~D, kolorvxl이 E~G, 6729skl이 H~J를 받았던 것 같습니다. 앉아있는 순서대로 문제지를 나눠 가졌습니다. 

제가 가장 먼저 잡은 문제는 A입니다. (기억은 안나는데 kolorvxl이 제가 A를 풀었다고 했습니다.) 범위를 보자마자 배열 A와 배열 B를 공통으로 사용하는 DP가 가장 먼저 떠올랐지만 여러가지 생각해보니 dp 포인터가 대각선에서만 움직이므로 선형 dp로 해결가능함을 관찰했습니다. (아마 그랬을겁니다.) 그래서 dp update가 선형 시간에 돌아도 됨을 확인하고 선형 시간에 동작하는 dp update 로직을 찾았습니다.

 

그 와중에 kolorvxl이 F를 빠르게 읽고 빠르게 구현했습니다. 실제로 전체 문제 중 가장 쉬운 문제였습니다. 그리고 시작한 지 7분만에 WA를 받았습니다.

F를 일단 6729skl이 디버깅하기로 하고 제가 A를 구현해 14분만에 AC를 받았습니다. I가 정수론이고 제가 팀에서 정수론을 가장 잘한다는 팀원들의 일방적인 주장에 따라 I를 고민하고 있었습니다.

 

kolorvxl이 I를 같이 고민하는 동안 6729skl이 F의 leading zero 반례를 하나 찾았습니다. 찾은 시점에 이게 마지막 남은 반례가 맞는 지 토의했습니다. 제가 세 번 고민 끝에 제출하자고 했고 18분에 AC를 받았습니다. 이 시점에서 13위까지 올라갔기 때문에 예상 이상으로 좋은 성적을 받을 수 있으리라 생각했습니다.

 

결국 I 풀이를 내기까지 오랜 시간이 걸렸습니다. 아무래도 근을 먼저 정해 두고 전개하는 관찰이 좋지 않았던 것 같습니다. 상수항이 이미 인수분해가 한 번 이상 된 꼴이라 더 말린 것 같습니다. kolorvxl이 근의 공식으로 가면 좋겠다고 한 뒤로 쉽게 해결되었습니다. 근의 공식을 쓰니 p가 가져야 할 restriction이 바로 보였습니다. p(p-4k)가 완전제곱이기만 하면 된다는 점을 바로 관찰했고, kolorvxl이 디테일을 챙겨 구현해 50분에 AC를 받았습니다. 다르게 식을 정리할 것 없이 p(p-4k)가 완전제곱인지만 봐도 충분했을 것 같습니다.

근의 공식 이전에 대칭이므로 개수만 알면 합은 쉽게 구할 수 있음을 알았지만 모든 경우를 탐색하는 것으로 개수를 알아내는 방법을 선택해 의미가 없었습니다.

 

I를 구현하는 동안 H를 읽었습니다. UCPC2024의 "산을 그리세요"와 비슷한 감성으로 dp를 챙겨보려 했습니다. 제법 오래 고민했던 것 같은 기억이 있으나 H를 언제부터 읽었는지 기억나지 않습니다. I의 AC 이후 kolorvxl과 함께 H를 고민했는데 제가 다른 문제를 잠시 보고 오는 사이 45도 돌리면 LIS가 되는게 아니냐는 풀이가 나왔습니다. 10초정도 보니 formal proof가 바로 보여서 kolorvxl이 구현을 시작했습니다. 6729skl과 함께 J를 읽었던 것 같습니다. 그 와중에 이분 탐색을 짜고 있길래 팀노트에 LIS 구현이 있다고 해서 빨리 구현을 완료할 수 있었습니다. 63분에 AC를 받았습니다.

 

H를 구현하는 동안 6729skl이 J를 읽고 그래프 모델링이 끝난 버전으로 설명해줘서 함께 고민했습니다. 기억하기로는 비슷한 유형의 문제를 연습 중에 깨달았던 것 같습니다. 2-path의 모임을 관리하면 200만개 이하의 에지만 관리해도 됨을 함께 관찰했습니다. 다만 degree 제곱의 합만큼 돌아야했는데 O(m^2)이 아닐까 하고 고민을 했습니다. 지금 생각하니 O(nm)에 바운드되는데 왜 관찰하지 못했나 싶습니다. kolorvxl이 구현했던 것 같고 67분에 AC를 받았습니다.

 

그리고 혼자 B로 넘어가고 팀원들이 C를 어떻게 잘 풀어왔습니다. C는 아직 풀이도 뭐도 잘 모릅니다. B,D,E를 번갈아 보며 뭐가 쉬운지 가늠했던 것 같습니다. 결국 B는 제가 문제를 잘못 읽어 예제가 왜 저렇게 나온지도 이해하지 못하고 마무리했고, E는 하나를 시간 축으로 삼는 구간쿼리 문제임을 짐작했지만 오프라인 쿼리를 써볼 생각을 하지 못했고, 마지막으로 D의 구성을 조금 생각해봤으나 결국 찾지 못했습니다. 

 

최종 스코어 6솔브 16등으로 마무리했습니다. 

진짜 중요한 얘기는 본선에서 나옵니다.

'Problem solving' 카테고리의 다른 글

Good Bye, BOJ 2025! 후기  (0) 2026.02.23
ICPC 2025 Seoul Regional Competition 후기  (1) 2026.01.08
BOJ 18457 Knowledge  (2) 2025.08.02
ICPC 2024 Regional Seoul 후기  (2) 2025.07.17
UCPC2024 후기  (0) 2024.09.01

문제

You have a string s consisting of lowercase English letters “a” and “b”.

You can make zero or more operations in any order. Here are the possible operations:

  • Delete “aa” from any place of the string.
  • Delete “bbb” from any place of the string.
  • Delete “ababab” from any place of the string.
  • Add “aa” to any place of the string.
  • Add “bbb” to any place of the string.
  • Add “ababab” to any place of the string.

Your goal is to calculate the number of strings of length x that can be obtained by such operations. As the answer can be very large, find it modulo 998 244 353.

입력

The first line of the input contains one integer n: the length of the string (1 ≤ n ≤ 300 000).

The second line contains a string s of length n consisting of lowercase English letters “a” and “b”.

The third line contains one integer x (0 ≤ x ≤ 109), the length of the string you need to obtain.

출력

Print one integer: the number of strings of length x that can be obtained from string s by making the operations described above, taken modulo 998 244 353.

풀이

문자열에 "aaa", "bbb", "ababab"를 자유롭게 추가하고 제거할 수 있다. 이 작업이 항상 역연산이 가능하므로 equivalence class의 원소 개수를 세는 문제임을 알 수 있다.

free monoid의 equivalence class문제로 생각해보자. 이는 어떤 free monoid homomorphism의 kernel로 생각할 수 있고, 항등원 e, "aa", "bbb"가 같은 class로 대응되기 때문에 a의 위수가 2, b의 위수가 3인 군임을 알 수 있다.

 

이제 군 연산을 이용해서 class의 구조를 알아내면 된다. 이 구조가 군이기 때문에 right multiplicative automota는 Caley graph에 대응이 되고, 그래프를 손으로 열심히 그려보면 정점 12개의 regular directed graph를 얻을 수 있다.

 

마지막으로 우리가 구하고자 하는 것은 주어진 문자열과 같은 class의 원소이면서 길이가 x인 원소의 개수이므로 정확히 Caley graph에서 [e] class에서 시작하는 distinct walk의 개수이므로 인접행렬의 거듭제곱으로 얻을 수 있다.

 

주어진 문자열이 어떤 class에 속하는지 구분하는 방법은 여러가지가 있는데, 가장 쉬운 방법은 Caley graph가 automata인 점을 이용해 선형 시간에 어떤 클래스에 속하는지 추적하는 방법이 있고, 아니면 정말 단순하게 주어진 연산 중 문자열 길이를 줄이는 연산을 적절히 찾아 monotone deque를 이용해 문자열을 재구성하는 방법이 있다. 전자의 경우는 automata의 구조를 미리 가지고 있어야 하므로 구조를 이미 알고 있는 경우 적합하고, 후자의 경우는 일반적으로 사용할 수 있으나 이 구조가 유한 모노이드임을 모른다면 유한 시간에 끝나기 어려운 방법이다.

후자의 방법으로 문자열을 재구성하기 위해서 "aa"->e, "bbb"->e, "ababab"->e 외에도 "abba"->"bab", "aba"->"bbabb", "babba"->"bbab"의 3개의 연산을 추가로 사용했다. "aba"->"bbabb"는 문자열의 길이가 늘어나는 것이 아니냐고 생각할 수 있는데, automata의 구성 과정에서 얻어진 변환이라 정확한 근거는 모르겠지만 저렇게 변환하면 모든 class를 얻을 수 있었다.

 

추가로 이 군은 A4와 동형이고, 실제로 이 문제를 A4(isomorphic to PSL2Z3)임을 group representation <a,b|aa,bbb,ababab>만으로 알아내 해결한 사람도 있다. (링크) 하지만 group representation은 상당히 어려운 주제이고, 열심히 문자열을 분류하는 것이 가장 쉬운 방법이 아닐까 싶다.

2025년이 되었지만 아직까지 작성하지 않고 미뤄 둔 ICPC 2024 Regional Seoul 후기글입니다.

 

2024년도는 전역 후 복학한 첫 해였다. 2021년 입학 당시 리저널에 대한 정보가 없어 참가하지 못했고, 복학한 뒤 열린 첫 ICPC였기 때문에 첫 ICPC 참가가 되었다. 2024 PNUPC 운영진으로 참가한 것을 계기로 Pulse에 가입하게 되었고, Pulse 내에서 팀을 구성하려 한 끝에 어찌저찌 팀이 만들어졌다.

 

이 팀명은 나중에 네임드가 됩니다.

팀명을 정하는 과정이 아주 멀고 힘들 것이라고 생각했지만 상당히 재밌는 아이디어가 나왔다. 마침 고려대학교 MatKor Cup에 제출된 @pizzaroot님의 코드가 아주 잠깐 화두에 올랐기도하고 해서 만장일치로 팀명은 JungSangHwa가 되었다. (정상화 교수님 수업은 아주 좋았습니다)

 

팀 연습 퍼포먼스가 좋지 않아 고민이 많았던 것 같다. 요코하마 리저널을 돌았던 것 같은데 지금 보면 다소 초라한 성적이 아닌가 생각한다.

 

 

지금 돌아보면 예선 결과가 마냥 좋지 않다.

 

E (+1, 00:11) 차분 행렬의 최대값을 찾는 문제로 환원가능했다. 상당히 괜찮은 관찰이었기 때문에 제일 먼저 구현했다. 빠르게 AC를 받을 뻔했으나 늘 그렇듯 구현이 말썽이었다. 패널티를 받은 이유는 기억나지 않는다.

 

H (+3, 01:37) 간단히 모든 경우를 다 시도해볼 수 있었다. @corn_tufu가 구현했다. 백트래킹으로 구현하다가 구현을 실수한건지 패널티를 크게 받았다. 물론 구현 시간도 많이 지났다. 그 사이 D/F번을 읽었다. 대회가 끝나고 복기할 때 백트래킹하지 않고 그냥 나이브 브루트포스로 할 수 있는거 아니냐며 질책했다. 쉬운문제를 돌아가면 큰일난다.

 

F (+1, 02:18) 그림에서 피자 정리가 생각난다. 항상 binary planar decomposition이 된다는 점을 관찰하는 것으로 시작했다. 선분의 정보가 상당히 곤란하게 주어졌다. point in polygon을 하듯 점의 패리티를 세는 풀이를 냈으나 상당히 구현에 어려움을 겪으리라 생각했다. @corn_tofu가 구현했다. 열심히 구현해서 AC를 받은 것 같은데 어떤 실수였는지 기억나진 않지만 사소한 실수 하나를 해 패널티를 받았던 것 같다.

 

D (0) 풀이는 상당히 빨리 나왔다. @light_potato가 그리디하게 내려가는 풀이를 만들었으나 다른 문제 구현하느라 구현하지 못했던 것으로 기억한다. 실제 구현 시도도 했으나 잘 구현되지 않았던 것 같다. 2-sat 풀이도 생각했으나 결국 그리디가 맞았다.

 

C (-10) 열심히 구현했다. 완성된 문자열을 지우는 문제로 환원하면 긴 접두사를 지우게 되면 더 적은 비용으로 해결할 수 있는 점에 착안해서 열심히 구현했으나 AC를 띄우지 못했다.

 

다른 문제도 보긴 했는데 잘 기억나지 않는다.

 

스코어보드정상화

하지만 C의 테스트케이스에 문제가 있었음을 공지받았고, 재채점 결과 60등까지 올라갔다. 사실 맞는 풀이였는지도 모르겠다. 아무튼 맞았으니까 된거 아닐까 싶었고, 리저널 본선에 가게 되었다.

 

 

A (+1, 01:23) 첫 솔브까지 상당히 오래 걸렸다. 엄청 어려운 문제가 아니었는데 구현에서 절었다. 경계값 같은 구현 디테일에서 상당히 죽을 쒔던 것 같다.

 

L (02:24) 상당히 빠른 시기에 풀이를 내고 @corn_tofu에게 구현을 요청했다. 무난하게 AC. 하지만 증명하느라고 구현을 늦게 시작했다. 증명까지 해줬어야했을까 싶다.

 

B (+2, 03:22) 그래프 문제임을 알아내는 데 한세월이 걸렸다. 문자열 문제로 단단히 착각하고 감을 잡는데 상당히 멀리 돌아 갔다. 동적으로 그래프탐색을 하면서 답을 찾는 풀이를 먼저 냈는데 구현을 시작하자마자 컴포넌트 크기로 해결할 수 있음을 깨달았다. 패널티를 받은 이유는 기억나지 않는다.

 

L (-5) 시뮬레이션 결과로 이분 탐색한다는 사실은 아주 빠르게 알아냈지만 구현이 잘못됐는지 AC를 받을 수 없었다. @light_potato가 구현했다. 이 문제에 들인 구현 노력 때문에 오히려 다른 문제를 보고 구현할 시간이 모자랐다.

 

D (-5) 이런 문제를 잡았었나?

 

F (0) 그냥 하면 된다는 사실을 깨닫는데는 오래 걸리지 않았는데, 수상한 바운드와 제한 안에 들여보낼 방법을 고민하다가 결국 풀지 못했다. 다시 풀어볼만한 문제인 듯하다.

 

모든 문제를 풀고 나서 퇴실 직전 남는 시간에 앞자리에 앉은 제주대학교 MerVermillon팀과 인사를 나눴다. 똑같이 아주 멀리서 왔기 때문에 동질감이 들었다.

 

모든 행사를 마치기 전에 먼저 귀가했다. 장거리 일정이어서 컨디션 관리에 실패한 것도 있고 여러 이유로 먼저 갔다. @mbae059가 다시 돌아오라고 했지만 버스를 타고 이미 떠난 시점이었다. 

 

https://youtu.be/_af5H9iqzDo?feature=shared

 

 

많은 분들이 관심을 가져주셨던 팀명이 유튜브를 탔다. (...)

이 영상이 올라오고 많은 분들이 연락해오셨다. 내년에는 더 재밌는 팀명으로 찾아뵙겠습니다. 감사합니다.

UCPC2024는 출전 못할 줄 알았다. 극적으로 디코에서 팀원을 구해서 신청하게 되었다. 

그래서 최종 팀원은 @rustiebeats, @ychangseok과 함께 나가게 되었다. 팀명 후보로는

"팀 이름 후보 드릴테니 이중에 정해주세요", "); DROP TABLE Teams;--", "어디갔어다", "1.팀 이름 후보 드릴테니 이중에 정해주세요 2. ); DROP TABLE Teams;-- 3. 어디갔어다" 정도를 제시했는데 "); DROP TABLE Teams;--"가 채택되었다.

 

')', ';' 등이 사용할 수 없는 문자여서 강제 리젝당했고 결국 가-힣ㄱ-ㅎㅏ-ㅣa-zA-Z0-9-_가 팀명이 되었다.

너무 흔한 팀명을 써서인가 결국 "정규식 팀"이 세팀이나 만들어지는 불상사가 생겼다...

 

 팀 연습부터 대회까지 @rustiebeats님이 ABCD, 내가 EFGH, @ychangseok님이 나머지 문제를 먼저 읽는 방향으로 문제풀이를 했다. 예선의 경우 A가 가장 쉬운 문제가 나오기 때문에 @rustiebeats님이 A번을 빨리 구현하고 스타트하게 되었다. 

 

주로 @rustiebeats님이 구현이 까다로운 문제를 맡아주셨고 @ychangseok님은 내가 구현하다가 틀린 코드 리딩과 반례찾기를 해주셨고 내가 아이디어를 내고 구현을 팀원한테 던지고 도망가는 역할을 맡았다. 개인적으로 스스로 직관력이 좋은 편이라고 생각하는데 직관에만 의존하는 나머지 에지케이스를 못보고 넘어가는 경우가 아주 많아서 상당히 도움이 되어주셨다.

 

예선에서는 @ychangseok님이 HIJK를 읽어주셔서 나는 맨 처음에 EFG까지 읽었다.

A (00:01) @rustiebeats님이 그냥 풀어주셨다. 여전히 무슨 문제인지 읽어보지도 않았다.

C (00:02) @rustiebeats님이 A를 그새 다 풀고 C가 스택이라고 하고 넘어갔다. 이게 뒤에서 크게 작용하는데...

G,H(00:03) EF를 읽고 당장 풀 문제는 아니다 싶어서 G로 넘어왔는데 쉽게 풀리겠다는 직감이 들어서 풀이 스케치를 시작했다. @ychangseok님이 구현을 시작하셨다.

G,H(00:05) G의 솔루션이 나왔고 H가 WA를 받았다. 이후 팀원 두 분이서 H를 계속 고민해주셨다.

E (00:19) G만 잡고있어서 지루했던건지 E를 갑자기 잡는다고 했다. 결국 1분도 안돼서 다시 G를 구현하러 감

G(00:27) 2번의 RE 끝에 AC를 받았다. DSU 프리셋과 빠른 타자속도만 있었으면 퍼솔도 노려볼만하지 않았나 하는 생각도 들었다. RE 사유는 100만 크기의 배열을 잡으려고 했으나 10만이라 쳐서...

E (00:29) E를 다시 고민하러 왔다. @rustiebeats님이랑 같이 고민을 하다가

E,C,D(00:31) @rustiebeats님이 솔루션을 내주셨다. 고전적인 dp/bfs에는 나보다 훨씬 식견이 높으신듯. 구현을 맡기고 C와 D를 읽으러 갔다. 여전히 E 푸는데 토의도 같이 했다.

D,J(00:39) D와 J의 풀이가 거의 동시에 나왔다. D는 내가, J는 @ychangseok님이 풀이를 냈다. 하지만 둘다 틀렸다.
E(00:49) E의 구현이 끝나고 AC를 받았다. 이때 상당히 가능성을 본 느낌을 받았으나... ...

UCPC2024 예선 희망편

J (00:55) 2WA를 받았다는 소식을 들었다. 그리고 세명 다 J로 모였다.
D(01:07) D를 WA받았다. 그냥 WA가 아니고 로직을 깨는 반례를 맞았다. 여기서 포기.

J(01:18) @ychangseok님이 짝수번째만 뒤집는 발상을 해주셨는데 내가 이해를 못하고 그냥 묵살해버렸다...

C(01:20) J만 잡고있어서는 답이 안나올 것 같아서 C와 번갈아 가면서 봤다. C는 @rustiebeats님이, J는 @ychangseok님이, 나는 두개를 번갈아가면서 봤다. 

C(01:30) C의 대략적인 솔루션을 제시했다. 정답 알고리즘의 명세를 @rustiebeats님한테 던지고 나는 예제를 손으로 테스트해봤다. 이쯤에서인가 @ychangseok님이 D를 구현시작하셨다. 세그트리를 비벼먹은거같은데 잘 모르겠다.

J(01:42) 불현듯 든 생각이 정답을 만들었다. 괄호 문자열이라는 생각을 하고 나서 계속 구현하던 @ychangseok님이 아닌 내가 구현을 새로 해서 AC를 받았다. 32비트정수만으로 짜서 1WA를 추가로 더 받긴 했다.

C(02:21) C를 구현시작했다.

C(02:34) C 구현을 마치고 WA를 받았다.

D(02:35) @ychangseok님이 구현을 마치고 AC를 받았다. C를 구현하는 나를 제외하고 H로 모였다.

C(02:46) C를 AC했다. 정말 정신없이 마무리했던가 대회가 끝난 뒤 왜 못풀었을까 하고 업솔빙을 시도함...

본인이 푼 문제도 모름

마지막까지 H를 붙잡고 있었지만 최종 스코어보드는 6솔브 51등으로 마무리했다. 사실 여기서 H를 풀었어야하는데 케이스워크 능력이 좀 부족하다고 느끼게 되었다.

그리고 결국 본선에 가게되었다. LG의 넓은 대관과 회장님의 넓은 아량으로 낮춰진 컷에 겨우 들어서 예선을 통과했다고 들었다. 감사합니다 ㅠㅠ

놀라운 사실!

 

연습 중에도 ychangseok님께 구현을 많이 떠넘긴 감이 있는 것 같은데 본 대회 중에도 열심히 코드를 짜주셨다...

본선 대비 팀 연습 중에: 증명은 나중에 해드릴테니 일단 신뢰해주세요

최종 본선 49등 5솔브로 마무리했다. (2등 올랐다!)

A(+182) 처음부터 고민을 좀 했던 문제였다. O면 +여야한다는건 퍼스트 솔브 이전에 깨달았지만 X일때 판정하는 법에서 off by one같은데서 좀 많이 고민했다.

B(x) 제법 고민을 했던 문제인데 다이어그램 그리는걸 실패해서 포기

C(+239) rustiebeats님과 ychangseok님이 금방 구현해내신 문제. 처음에 이상한 구현문제라서 패스했는데 갑자기 rustiebeats님이 "아니 이거 개쉬운데"하고 금방 구현해내셨다. 이거 처음에 잡았으면 퍼솔도 먹고 패널티도 많이 줄었을 듯. 두분이 뚝딱 풀어주셔서 L번에 매진해서 고민할 수 있었고 해설 공개때까지 문제를 읽어보지도 못하고 AC당했다.

D(+25) 팀 내 첫 솔브 문제이다. 사실 문제 지문은 다 안읽었고 rustiebeat님이 문제를 빨리 해석해서 요약본을 주신 것만 읽고 바로 풀었다. 최대한 균등하게 나눠주면 되는 문제였고 연습때 쌓인 업보인지 나보고 구현해 달라는 요구를 하셨다... 결국 생 라이브로 짜긴 함.

E(+298) 맨 처음 읽은 문제인데 처음에는 그냥 넘기고 다시 돌아와서 봤을 때는 연속된 1과 2로 둘러싸인 빈칸을 잘 처리해주면 된다는 것이었다. 150분즈음에 답을 얻었지만 확신없음+구현이슈때문에 뒤로 미뤄뒀다. 결국 나 아니면 상대가 먹어야 한다는게 대칭 포인트였는데 엄밀한 증명에는 실패하고 PBA를 시도했다.

J(x) HLD 아이디어는 문제를 보자마자 캐치했지만 HLD를 짤 줄 몰라서 시도도 못한 문제. 하지만 HLD가 정해가 아니라는것에 1차 충격, 하지만 HLD로 푼 팀이 대부분이고 정해대로 푼 팀이 한 팀밖에 없었다는 것에 2차 충격

L(-1) 이걸 풀었어야 하는데 실패했다. 이분 매칭에 대한 이해 없이 그냥 무지성으로 플로우를 흘리는 코드를 외우기만 했어서 벌어진 참사라고 생각한다. 아이디어 자체는 작은 정점부터 플로우를 흘리면 된다는걸 알았지만 코드를 짜는법을 몰라서 AC를 받지 못했다. 이분매칭 공부다시할것

M(+290) 다른 문제는 잘 모르겠지만 이 문제는 오류가 있다. +290분이 아니라 +297분에 AC를 받았기 때문. 종료 직전에 버저비터를 눌렀다고 생각했지만 특별상 조건인 마지막 AC를 받지 못했다. 많이 아쉬울 따름.

문제 자체는 +30분 이전에 다이어그램은 혼자 머리속으로 그려보긴 했으나 풀이 방향을 완전히 다른 방향으로 잡아서 실패. rustiebeats님이 중간에 캐치해서 마지막의 마지막에 AC를 받았다. 대회가 끝나고 "산을 그리세요"를 얼마나 들은건지 잘 모르겠다.

 

끝나고는 참가자 몇 분과 대회 후원자분 하고 뒷풀이를 가졌다. 오래 하지 않은 것으로 들었는데 집가는 열차 시간 때문에 중간에 나오게 되었다.

집가는 SRT경부선에 탄 시점은 휴대폰이 1%남았던 시점... 하마터면 지하철에서 못내릴뻔

 

후기

1. 플래티넘 랜덤 디펜스는 상당히 중요하고 실력 향상에 도움이 된다.

2. 구현력을 키워야함. 

3. 보조배터리를 잘 챙기자

'Problem solving' 카테고리의 다른 글

BOJ 18457 Knowledge  (2) 2025.08.02
ICPC 2024 Regional Seoul 후기  (2) 2025.07.17
Solved.ac 그랜드 아레나 파티 (GA4Div2 On-Site) 후기  (4) 2024.02.05
BOJ 파댕이컵 출제 후기  (4) 2023.12.17
BOJ 춘배컵 출제 후기  (0) 2023.11.04

+ Recent posts