안녕하세요. 서울대학교 WE ARE BUT MEN, ROCK! 팀의 유키(lewha0) 입니다. 야밤에 심심하여(사실은 오게임 때문에) G번 에디토리얼을 쓰다 말고 후기 올립니다 =_= 에디토리얼은 아무래도 맑은 정신에서 써야할 것 같은데 후기는 지금 써도 될 것 같더라고요 으흐흐
프랙티스 세션.. 부터 간략히 써보면.. 서울 대회는 프랙티스 세션에서 예전 문제를 자주 활용하는 것 같은데, 올해는 두 문제 모두 재활용 문제였습니다.
원래 계획은 저희 팀의 마스코트 지러가 VS 프로젝트 설정을 해두고, 그동안 다른 팀원들은 문제를 읽는 것이었습니다. 그런데.. B번 문제를 보는 순간 저는 A번 문제에 대한 욕심이 싹 사라졌습니다 -_-; 난이도가 극악은 아니었음에도 작년에 아무도 못 풀었던 룸메이트 문제가 나왔거든요. 제가 알기로는 tanso judge에서 이 문제를 푼 사람이 딱 두명인데, 그 두명이 모두 저희 팀이었습니다 [....]
하필 다른 한 명인 도맹이 실험 때문에 참가하지 못해서 제가 계속 B를 잡고 풀었습니다. 작년 이맘때쯤에 이 문제에 도전했었는데, 한 가지 케이스를 처리하지 못해서 계속 WA가 떴기 때문에 불안했습니다. 그래서 일부러 케이스를 세세하게 나누고, 최적해가 아닐 것 같아 보여도 feasible한 다양한 경우를 고려하는 다이나믹으로 풀었습니다. 중간에 학교에서 전화도 걸려오는 등의 태클이 있었지만.. 그래도 한 번만에 AC를 받을 수 있었습니다.
그 후에는 지러가 java IO를 연습했습니다. 작년 같은 경우에는 남는 시간에 다양한 소스를 섭밋해보며 Time Limit에 대한 측정을 했었는데, 보낼 때마다 조금씩 결과가 달라서 그다지 믿을만한 내용은 아니었습니다. 연습 때 BigInteger를 활용할 기회가 몇 번 있었기 때문에, 혹 대회 때에도 java를 써먹을 일이 생기지 않을까 하여 연습을 했는데.. 결과적으로는 쓸 일이 없었네요 ㅡ,.ㅡ
그리고 대회날이 되었습니다. 트렁크 사건의 주인공답게 이번 대회때도 이것 저것 텍스트를 많이 준비해 갔었는데요, 그 중에는 신묘한 문제도 하나 섞여 있었습니다. 대회 시작 전에 그 문제에 대해서 팀원들과 잠깐 얘기를 해 보려 했었는데(솔루션도 인쇄해서 가져갔기 때문에 그 솔루션이나 공부할까 해서요), 팀원들이 왜 문제를 하나 늘리려 하냐고 구박하더군요 ㅡ,.ㅡ
그리고 징소리와 함께 대회 시작! 계획대로 지러는 프로젝트 셋팅을 하고, 저는 A번 문제를 읽고, 도맹은 B번 문제를 읽었습니다. 매년 그랬듯 A(MOLAR)번 문제는 쉬운 편이였습니다. 문제도 제대로 못 읽었지만, 딱 봐도 뻔한[....] 문제였기 때문에 바로 구현에 들어갈 수 있었습니다. 중간에 잠시 제약 조건을 살펴보고, 분자량(라기보다는.. 부분분자량?)이 최대 두 자리라는 점을 이용하여 if가 몇 개 들어간 방식으로 풀었습니다. 그리고 테스트를 해 봤는데, 이상하게 두 번째 케이스가 답이 크게 나오는 겁니다! 초기화를 안 했나 싶었는데, 세 번째 케이스부터는 또 답이 제대로 나오더라고요. 잠시 패닉 상태에 빠져 있다가.. 소스 코드도 들여다보고 직접 계산도 해봤는데, 알고보니 입력을 잘못 넣은 것이었습니다. 알파벳 O가 숫자 0과 비슷하여 그만 실수를 했더라고요 -_-
약간의 삽질 끝에 A번을 조금 늦게 섭밋하고 나왔더니, 도맹이 저에게 B(Editor)번 문제를 넘겨 주었습니다. 얼핏 보기에는 쉬워 보이는 문제인데 바로 해법이 떠오르지는 않는다고 하더라고요. 문제 설명을 듣는 순간, DP종만님의 뒤를 잇고 싶은 DP유키답게 바로 DP 솔루션이 떠올랐습니다. 문제를 읽어보면서 두 스트링이 겹칠 수 있다는 점을 확인하고, memoization을 이용한 간단한 코드를 바로 작성하여 섭밋했습니다. 그러나 결과는 RE?! 잠시 패닉 상태에 빠져 있다가, index가 음수가 되는 경우를 처리하지 않았음을 깨닫고는 바로 수정하여 섭밋했습니다. 그렇게 팀의 첫 번째 추가 페널티를 받으면서 B를 풀었습니다.
그동안 다른 팀원들은 문제를 하나씩 읽고 있었습니다. 도맹은 J(Number)번부터 봤던 모양인데, 아리까리하지만 그리디와 다이나믹을 적절히 섞으면 풀릴 것 같다는 이야기를 했습니다. 저도 잠깐 문제를 봤지만 명쾌한 답이 떠오르지 않아 보류했습니다. 도맹도 잠시 이것저것 생각해 보다가, 아마 C(Network)번 문제를 잡았던 것 같습니다. C번은 다이나믹 쪽으로 접근했던 것 같은데, 역시 명쾌한 답안은 아니라서 계속 검토해보는 중이었습니다. 그동안 지러는 D(Phone)번 문제가 쉽다는 걸 깨닫고는 풀기 시작했습니다. 저는 남아있는 문제들을 훑어보며 다음 풀 문제를 찾기 시작했습니다.
E(Tile)번 문제는 그림부터 어쩐지 익숙하였기에, 간단한 DP 문제일 것이라 예상했습니다. 아마 제가 지러에게 E를 좀 봐달란 얘기를 했었던 것 같은데, D번이 더 쉬웠는지 D번을 풀기 시작했습니다. 다음 문제로 대충 E(Tile)를 밀어넣고는 F(Meteor)를 살펴 보았습니다. 아마 곳곳에 가득한 nhn 때문에 문제에 겁을 먹었던 것 같은데, 대충 그림만 보고는 "frame을 어느 위치에 배치해야 가장 많은 meteor를 포함하겠느냐"는 몹시도 어려운 문제라 생각했습니다. I번은 그림이 만만찮아 보여서 넘어갔고, 다음으로 눈에 들어온 문제는 G였습니다.
G(Signals)도 처음에는 그림만 보고 굉장히 어려울 것이라 생각했는데, 처음에는 회로의 저항값을 구하여 두 회로의 저항값을 비교하는 문제라 생각했습니다. 하지만 두 번째 페이지의 =, <, >, 0, 1의 다양한 출력을 보면서 뭔가 이상하다는 느낌을 받았고, 문제를 잘 읽어보니 그렇게 어렵지만은 않은 문제라는 생각이 들었습니다. 관건은 생성될 수 있는 스트링의 수가 최대 얼마까지 가능하냐 였는데, 도맹과 토의하면서 2^10, 3^7을 계산해 보고는 그렇게 크지는 않다는 걸 깨달았습니다. 저는 다음 문제로 G를 잡고 파싱을 어떻게 할지를 생각해 보고 있었습니다.
그동안 지러는 D(Phone)번을 성공적으로 풀었고, 제가 G(Signals)번 문제를 잡고 들어갔습니다. C(Network)번과 G번 중에 어느 것을 먼저 택할지 잠시 고민했는데, C번이 그렇게 깔끔하지는 않을 것 같아서 G번을 먼저 잡게 되었습니다. 그러는 동안 다른 팀원들에게는 E를 주었는데, 대칭인 경우 때문에 생각보다는 까다로운 문제였습니다. 하지만 지러가 문제를 잡고 고민하기 시작했고, 도맹은 대강 C(Network)를 정리하고 H(Puzzle)를 보기 시작했습니다. 비슷한 문제를 접해 본 적이 있었다는 얘기를 하면서, 아마 오토마타를 그리기 시작했던 것 같습니다.
그리고 저는 이때부터 랭킹을 제대로 보기 시작했습니다. 페이스는 상대적으로 썩 나쁜 편은 아니었는데, 그래도 시간이 제법 걸리는 편이었던 것 같습니다. 순위를 대강 살펴보며 마이티 프랜드가 생각보다 강하다는 것과, 그리고 team38이 예상 외의 다크호스라는 느낌이 들었습니다. 아마 아실 분은 아시겠지만, 대회장 왼쪽 주변에 유망한-_-; 팀들이 제법 몰려 있었기 때문에 team38이 의외라는 생각을 했던 것 같습니다.
아무튼 저는 G(Signals)를 잡고 풀기 시작했는데, 파싱에 대해서 깊게 생각해보지 않고 문제를 잡았기 때문에 꽤나 고생했습니다. 두 가지의 섣부른 가정을 했었는데, 하나는 | 연산자가 (괄호 안에서) 최대 하나뿐이라는 것이었고, 다른 하나는 모든 | 연산자들이 괄호 안에만 위치한다는 것이었습니다. 게다가 구현마저도 제대로 되지 않아서, 중간 결과를 찍어보면서 또 패닉 상태에 빠지게 되었습니다. 그동안 지러가 E(Tile)번 문제의 식을 정리했고, 저는 컴퓨터 앞에서 물러나 G번의 현재 코드를 어떻게하면 쉽게 고칠 수 있을지를 고민했습니다.
그동안 도맹은 H(Puzzle)번 문제를 분석하다 말고 F(Meteor)번 문제를 읽고 있었습니다. 제가 굉장히 어려운 문제처럼 팀원들에게 전달했지만, 도맹은 문제를 잘 읽어보고는 제가 생각했던 것 보다는 쉬운 문제라는 걸 알아냈습니다. 다만 조금 까다로운 부분이라면 폐구간이 아니라 개구간들을 다뤄야 한다는 점과, 실수 처리에서 오차가 생길 수 있다는 것이었습니다. 그렇다고는 해도 해법 자체는 그렇게 까다롭지 않았기 때문에, F번 문제보다는 H번 문제에 집중했던 것 같습니다.
한 번 정도의 실수가 있었지만 지러가 E(Tile)번 문제를 잘 풀어 주었고, 저는 그동안 G(Signals)번 코드를 수정하고 있었습니다. 대회 때의 통계를 보니까 이 때의 간격이 제법 길었네요. E번이 53분이었고, G번이 115분이었으니 거의 한시간 정도의 간격이었습니다. 저는 G번 소스를 계속 수정하고 있었고, 그 와중에 지러가 I번 문제를 읽어보고, 풀고 있었습니다. G가 생각만큼 잘 풀리지 않아서 제가 많이 당황했었던 것 같은데, 팀원들이 차분히 하면 될꺼라는 식의 위로를 해 주어 고맙.. 긴 했는데 후배들 앞에서 이게 뭔 추태냔 생각을 했습니다 OTL 그 와중에 잠시 화장실에도 다녀오면서 team38의 정체를 슬쩍 보았는데, 놀랍게도 파키스탄에서 온 팀이었습니다! 뭐 그 때까지는 인도 팀인줄 알았지만..
우여곡절 끝에 제가 G(Signals)번 소스를 완성했고, 2^10과 3^7의 데이터를 넣어 보고는 생각보다 빠른 속도에 만족하여 섭밋을 했습니다. 코드가 복잡한 편이었지만, 그래도 한 번에 AC를 받을 수 있었습니다. 중간 중간에 디버깅하는 짬을 이용하여, 지러는 동시에 I(Turtle)번 문제를 풀고 있었습니다. 중간에 지러가 I번 문제에 대한 질문도 보냈었는데, 그러면서 생각을 잘 정리했는지 I번 문제를 제법 빠르게 풀어냈습니다. 이때까지만 해도 페이스가 굉장히 좋았고, 다른 문제들에 대한 해법도 다 나와있는 상황이었기 때문에, J번 문제를 푸는 팀이 1등을 하거나, 혹은 나머지 아홉 문제에서 페널티 싸움이 일어날 거라는 생각을 했었습니다. 하지만.. Life is not that easy.
이제 남아있는 문제는 C(Network), F(Meteor), H(Puzzle), J(Number). 도맹은 C, F, H를 잡고 있으면서 모든 문제에 대한 해법을 내놓았지만, 다른 팀원들에 밀려 컴퓨터는 잡지 못하는 상황이었습니다 -_-; 다른 문제가 풀리는 동안 지러와 도맹은 C번 문제에 대한 토의를 했고, C번 문제가 복잡한 DP보다는 간단한 그리디로 풀릴 것 같다는 결론을 내렸습니다. 그러면서 C번은 도맹의 손을 떠나 지러에게로 넘어갔습니다. 간단한 해법에 비해 복잡한 numerical 문제 때문에 코딩에 들어가지 못했던 F번은 어느덧 제 손으로 들어와 있었습니다. 어버버버.. 어쩔 수 없이 도맹은 H번에 집중해야 했습니다 ;ㅅ;
아마 다음 문제는 C(Network)번이었던 것 같은데, 지러가 이런저런 생각을 하면서 코딩에 들어갔습니다. 그동안 저는 F(Meteor)번 문제를 준비하고 있었습니다. 어차피 문제에서 나오게 되는 계산식들은 모두 정수 계수였기 때문에, 저는 시간을 실수형이 아니라 유리수(fraction)로 표현해야 겠다는 생각을 했습니다. 준비해간 frac class를 꺼내들고(thanks to JM!) F번 문제에서 사용되는 식들을 정리하고 있었습니다. 그동안 지러는 C번은 풀었지만 WA를 받았고, 소스 코드를 인쇄하여 디버깅을 시작했습니다. 그리고 저는 F번 문제를 들어갔습니다.
일단은 준비해 간 frac class를 열심히 따라 치고 있었는데, 나중에 생각해보니 결국 필요한 건 비교 연산자 뿐이었습니다 -_- 준비해 간 코드에서는 비교 연산자가 빠져 있었는데, set이나 map에서 사용할 수 있도록 적당히 순서만 주어져 있는 비교 연산자였습니다. 하여, 저는 팀원들에게 비교 연산자 부분을 채워달라는 부탁을 하면서 F번을 풀고 있었습니다. 약간 까다로운 면이 있기는 했지만 팀원들이 금방 정리를 해 주었고, 덕분에 별 어려움 없이 frac class를 완성할 수 있었습니다.
F(Meteor) 문제에서 제일 까다로운 부분은 시간을 구하는 부분이었는데, 도맹이 x축과 만나는 시간과 y축과 만나는 시간의 intersection을 구하면 될 것 같다는 조언을 해 주었습니다. Frac class를 사용했기 때문에 intersection을 구하는 게 조금 까다롭기는 했지만, 어쨌든 소스 코드를 완성할 수 있었습니다. 하지만 예제를 넣어보니 런타임 에러가 났고, gcd 함수에서 0으로 나누는 실수가 있었던 기억 때문에 gcd 함수를 적당히 수정했습니다. 그리고 섭밋했지만, 결과는 WA였습니다. 결국 저도 지러처럼 소스코드를 인쇄해서 디버깅을 시작했습니다.
그동안 도맹은 대망의 H(Puzzle)를 코딩하기 시작했습니다. H는 구현이 까다로운 문제였기 때문에 시간이 많이 걸릴 것 같았습니다. 그래서 중간에 저와 지러가 잠깐씩 컴퓨터를 잡아가며 자신의 소스를 돌려보고, 수정하는 식으로 가닥을 잡았습니다.
제가 한 실수는 속도 벡터의 한 축 성분이 0일 경우였습니다. 이 경우에는 유성이 한 쪽 축에 평행하게 이동하기 때문에, 다른 축 방향으로는 사각형과 교점이 생기지 않습니다. 뒤늦게 이 실수를 발견하고 수정했지만, 결과는 다시 WA. 이런저런 케이스를 넣어 보다가, 예제도 안 나오는 솔루션이라는 걸 알게 되었습니다. 다시 또 패닉 상태에 빠져들면서 한참 소스코드를 들여다봤고, 속도 벡터의 한 성분이 0일 경우에 무조건 사각형과 교차한다고 짠 것을 발견하게 되었습니다. 이 부분을 수정하여 다시 제출한 끝에 AC를 받을 수 있었습니다.
도맹은 여전히 H를 풀고 있었고, 저는 지러의 코드를 같이 살펴 보았습니다. 지러는 특이하게도 stack을 사용하여 non-recursive한 구현을 사용했는데, 제게는 익숙하지 않은 방식이었기 때문에 소스 코드를 이해하기 어려웠습니다. 그래서 일단 방법에 대한 토의를 시작했는데, 이 때에도 같은 내용을 서로 다른 식으로 표현하는 바람에 의사소통이 어려웠습니다. 그러다보니 자꾸 시간만 흐르게 되었고, 여전히 C는 풀리지 않은 상태였습니다. 그 와중에 도맹은 H번의 코드를 완성했지만, 예제가 잘 나오지 않았습니다. 답 자체는 맞게 구하는데, 초기화에 이상이 있는지 입력 순서를 바꾸면 잘못된 답이 나오기도 했습니다. 아홉 문제에서 열 문제를 사이에서 1등이 나올 거라는 생각을, 이 때부터 부정하게 되었습니다.
한 시간 정도가 남은 시점이었기 때문에, 이 무렵에 팀원 사이에 약간의 갈등이 생겼습니다. 두 문제를 잡을 것인지, 아니면 한 문제에 집중할 것인지. H번은 전적으로 도맹의 손에 달린 문제였고, C번 문제의 알고리즘이 그렇게 복잡한 것은 아니었기 때문에, 제가 recursive하게 C번을 다시 구현해 보는 쪽으로 가닥을 잡았습니다. 그 와중에 도맹은 H번 소스를 완성했는데, 결과는 TLE였습니다. 제가 C번을 다시 짜는 동안 도맹과 지러는 H번 소스를 같이 살펴보면서, 속도가 개선될만한 부분을 고쳐다가기 시작했습니다.
하지만 저 역시 C번을 깊게 생각해 본 것은 아니었기 때문에 조금씩 꼬이게 되었습니다. 도맹은 H번 소스를 조금씩 고쳐봤지만, 결과는 여전히 TLE 였습니다. 정신없기 고치다보니 아마 중간에는 예제도 안 나오는 상황이 있었던 것 같습니다. 방법 자체는 맞다는 생각을 했고, 이론적으로는 시간 내에 나올법한 시간복잡도 였기 때문에, 섭밋 자체를 여러 번 시도했었습니다. 똑같은 코드를 VS로도 섭밋해보고, g++로도 섭밋을 해 봤는데, 이 와중에 제출을 잘못하여 몇 번 컴파일 에러를 받기도 했습니다.
H번이 여러 번의 시도에도 불구하고 TLE였기 때문에, 저와 지러가 함께 제 C번 소스를 보는 쪽으로 가닥을 잡게 되었습니다. 물론 도맹은 옆에서 계속 H번을 개선할 방법을 찾고 있었고요. 알고리즘부터 명확히 해야겠다는 생각에, 지러에게 제가 구현하려 하는 방식을 이야기 해 주었습니다. 그리고 이게 저희 팀의 승리 비결이었습니다. 제가 무심코 말한 한 마디에 지러가 자신의 실수를 발견했거든요. "아 맞다, leaf만 고려하는 거구나!"
이 때문에 다시 또 갈등에 빠졌는데, 지러의 기존 C번 소스를 잘 수정하는 쪽으로 방향을 잡았습니다. 그 와중에 도맹도 H번을 최대한 고쳐 보기로 하고요. 하지만 H번 보다는 C번이 쉬웠기 때문에, 저는 개인적으로 C번을 잡는 쪽으로 생각을 했었습니다. 도맹은 H번 최후의 수정안을 내놓았고, 소스를 수정하여 섭밋했습니다. 슬슬 페널티가 큰 의미없는 시점이 되었기에, 똑같은 코드를 바로 g++로도 섭밋했습니다. 그리고 결과도 보지 않고, C번에 집중하기로 하였습니다. 이 때가 대회 종료 10여분 전이었습니다.
제가 지러의 C번 소스코드를 제대로 읽었더라면 좋았겠지만, 대충만 봤었기 때문에 결국 지러가 혼자서 자신의 코드를 수정해야 했습니다. 지러는 이것 저것 수정을 시작했고, 중간중간에 컴파일도 해보고 테스트도 해봤습니다. 지러는 일단 예제가 나오는 단계가 되면 무조건 섭밋을 하자고 했고, 저 역시 그게 좋겠다는 생각이 들었습니다.
다들 비슷하겠지만, 이 마지막 몇 분이 정말 피말리는 순간이었습니다. 지러는 계속 소스코드를 수정하고, 섭밋하고, 다시 수정하고, 수정하다 말고 아까 섭밋한 결과를 보고, 계속해서 수정하고, 섭밋하고, 다시 수정하는 과정을 계속 반복하고 있었습니다. 그 와중에 저 앞쪽에서 극적인 AC를 받았는지, 제법 큰 환호성이 들려왔습니다. 사진 기사분들이 바로 그 곳으로 달려갔지만, 저희는 그쪽에 신경 쓸 겨를도 없었습니다. 두 번짼가 세 번짼가의 섭밋을 마치고, 다시 소스 코드를 수정하고 있었을 때 극적인 메시지가 날아왔습니다. Yes - Accepted! 종료를 3분 앞두고 날아온 이 메시지에 저희 팀도 환호성을 지를 수밖에 없었습니다 ^^;
Team38이 매우 잘 하고 있었고, 앞쪽에서 들려온 환호성의 정체를 알 수 없었지만, 그래도 초반에 페널티를 많이 아낀 덕분에 2위 정도는 할 수 있으리란 예감이 들었습니다. 남은 3분동안 H번의 수정에 몰두해야 겠지만 저와 지러는 완전 탈력 상태였고, 도맹도 패닉 상태에서 빠져나오기 못했기 때문에 3분은 아무 일 없이 흘러갔습니다. 괜히 환호성을 지른 덕분에 사진 기사분들이 달려오셨고, 저희는 너무 부끄러워 절로 범죄자처럼 고개를 숙이게 되었습니다 ㅡ,.ㅡ
이렇게 대회가 끝나고- 저는 또 개인적인 사정 때문에 잠시 자리를 비우게 되었습니다 -_-; 한참 자리를 비웠다가 대회장으로 돌아왔고, 아마 시상식이 시작되기 30분 전이었던 것 같습니다. 다시 학교 사람들과 만나서 대회에 대한 이야기를 시작했고, 예상 외로 잘했던 team38에 대한 이야기를 꺼냈습니다. 그리고 저는 그 때 처음 team38이 실격 처리 되었다는 얘기를 들었습니다;; 또 지러가 대회가 종료된 후 ZSU 학생이 와서 페널티가 1152을 넘냐고 물어봤다는 얘기를 해 주었습니다. 이런저런 얘기들 때문에- 솔직히 말하자면 이 때부터 1등을 했다는 생각이 들기 시작했습니다 ^^;
그리고 시상식- 뒤늦게 대회장으로 달려오신 JM 님께서, 그리고 다른 OB 분들게서 Tribute 세레모니를 하라는 압박을 주셨지만- 차마 할 수는 없었습니다 -_-a 그리고 마침내 Grand prize 시상- 4년만에 처음으로 올라보는 자리였지만- 어떤 느낌인지는 잘 기억나지 않네요. 그저 그 위에서 찍힌 짤방 소스 하나만이 남은 것 같습니다.
축하해주신 모든 분들께 이 자리를 빌어 감사의 말씀을 드립니다. 모든 분들의 이름을 적고 싶지만, 대회장에서 박수를 쳐 주셨던 모든 분들의 이름을 다 적을 수는 없기에 생략 하겠습니다 ^^; 그리고 대회장에 못 오셨지만, 알고스팟에서 계속 응원해 주셨던 분들께도 감사의 말씀을 드립니다.
아직 올해의 대회는 끝나지 않았습니다-! 지역 대회들도 이제 시작일 뿐이고, 아직 접수도 안 끝난 대회들도 많이 있는 걸로 알고 있습니다. 아무쪼록 많은 분들께서 남은 대회들에 도전하시고, 좋은 결과를 거두셨으면 좋겠습니다. 그래서 올해 월드파이널에서 많은 한국 분들을 만날 수 있기를 희망하며, 혹 그렇지 못하더라도 다들 만족할만한 결과를 거두시기를 희망합니다.
그리고, 끝으로 이 긴 글을 읽어주신 분들께 또 감사의 말씀을 드립니다 ㅎㅎ
예전 외국의 모 대회에 나갔을때는 대회 끝나서 accept 를 받는 진풍경이 벌어졌였던 기억이 나네요.
judge 들이 채점을 워낙 늦게 해줘서 ;;; 대회끝나고 한 5분인가 10분인가 지나고 기다리고 있는데
Judge's respone : Yes 라는 상콤한 메세지가 날라와서
뒷북(?) 함성을 질럿던 기억이 나네요.
lewha0
안녕하세요. 서울대학교 WE ARE BUT MEN, ROCK! 팀의 유키(lewha0) 입니다. 야밤에 심심하여(사실은 오게임 때문에) G번 에디토리얼을 쓰다 말고 후기 올립니다 =_= 에디토리얼은 아무래도 맑은 정신에서 써야할 것 같은데 후기는 지금 써도 될 것 같더라고요 으흐흐
프랙티스 세션.. 부터 간략히 써보면.. 서울 대회는 프랙티스 세션에서 예전 문제를 자주 활용하는 것 같은데, 올해는 두 문제 모두 재활용 문제였습니다.
원래 계획은 저희 팀의 마스코트 지러가 VS 프로젝트 설정을 해두고, 그동안 다른 팀원들은 문제를 읽는 것이었습니다. 그런데.. B번 문제를 보는 순간 저는 A번 문제에 대한 욕심이 싹 사라졌습니다 -_-; 난이도가 극악은 아니었음에도 작년에 아무도 못 풀었던 룸메이트 문제가 나왔거든요. 제가 알기로는 tanso judge에서 이 문제를 푼 사람이 딱 두명인데, 그 두명이 모두 저희 팀이었습니다 [....]
하필 다른 한 명인 도맹이 실험 때문에 참가하지 못해서 제가 계속 B를 잡고 풀었습니다. 작년 이맘때쯤에 이 문제에 도전했었는데, 한 가지 케이스를 처리하지 못해서 계속 WA가 떴기 때문에 불안했습니다. 그래서 일부러 케이스를 세세하게 나누고, 최적해가 아닐 것 같아 보여도 feasible한 다양한 경우를 고려하는 다이나믹으로 풀었습니다. 중간에 학교에서 전화도 걸려오는 등의 태클이 있었지만.. 그래도 한 번만에 AC를 받을 수 있었습니다.
그 후에는 지러가 java IO를 연습했습니다. 작년 같은 경우에는 남는 시간에 다양한 소스를 섭밋해보며 Time Limit에 대한 측정을 했었는데, 보낼 때마다 조금씩 결과가 달라서 그다지 믿을만한 내용은 아니었습니다. 연습 때 BigInteger를 활용할 기회가 몇 번 있었기 때문에, 혹 대회 때에도 java를 써먹을 일이 생기지 않을까 하여 연습을 했는데.. 결과적으로는 쓸 일이 없었네요 ㅡ,.ㅡ
그리고 대회날이 되었습니다. 트렁크 사건의 주인공답게 이번 대회때도 이것 저것 텍스트를 많이 준비해 갔었는데요, 그 중에는 신묘한 문제도 하나 섞여 있었습니다. 대회 시작 전에 그 문제에 대해서 팀원들과 잠깐 얘기를 해 보려 했었는데(솔루션도 인쇄해서 가져갔기 때문에 그 솔루션이나 공부할까 해서요), 팀원들이 왜 문제를 하나 늘리려 하냐고 구박하더군요 ㅡ,.ㅡ
그리고 징소리와 함께 대회 시작! 계획대로 지러는 프로젝트 셋팅을 하고, 저는 A번 문제를 읽고, 도맹은 B번 문제를 읽었습니다. 매년 그랬듯 A(MOLAR)번 문제는 쉬운 편이였습니다. 문제도 제대로 못 읽었지만, 딱 봐도 뻔한[....] 문제였기 때문에 바로 구현에 들어갈 수 있었습니다. 중간에 잠시 제약 조건을 살펴보고, 분자량(라기보다는.. 부분분자량?)이 최대 두 자리라는 점을 이용하여 if가 몇 개 들어간 방식으로 풀었습니다. 그리고 테스트를 해 봤는데, 이상하게 두 번째 케이스가 답이 크게 나오는 겁니다! 초기화를 안 했나 싶었는데, 세 번째 케이스부터는 또 답이 제대로 나오더라고요. 잠시 패닉 상태에 빠져 있다가.. 소스 코드도 들여다보고 직접 계산도 해봤는데, 알고보니 입력을 잘못 넣은 것이었습니다. 알파벳 O가 숫자 0과 비슷하여 그만 실수를 했더라고요 -_-
약간의 삽질 끝에 A번을 조금 늦게 섭밋하고 나왔더니, 도맹이 저에게 B(Editor)번 문제를 넘겨 주었습니다. 얼핏 보기에는 쉬워 보이는 문제인데 바로 해법이 떠오르지는 않는다고 하더라고요. 문제 설명을 듣는 순간, DP종만님의 뒤를 잇고 싶은 DP유키답게 바로 DP 솔루션이 떠올랐습니다. 문제를 읽어보면서 두 스트링이 겹칠 수 있다는 점을 확인하고, memoization을 이용한 간단한 코드를 바로 작성하여 섭밋했습니다. 그러나 결과는 RE?! 잠시 패닉 상태에 빠져 있다가, index가 음수가 되는 경우를 처리하지 않았음을 깨닫고는 바로 수정하여 섭밋했습니다. 그렇게 팀의 첫 번째 추가 페널티를 받으면서 B를 풀었습니다.
그동안 다른 팀원들은 문제를 하나씩 읽고 있었습니다. 도맹은 J(Number)번부터 봤던 모양인데, 아리까리하지만 그리디와 다이나믹을 적절히 섞으면 풀릴 것 같다는 이야기를 했습니다. 저도 잠깐 문제를 봤지만 명쾌한 답이 떠오르지 않아 보류했습니다. 도맹도 잠시 이것저것 생각해 보다가, 아마 C(Network)번 문제를 잡았던 것 같습니다. C번은 다이나믹 쪽으로 접근했던 것 같은데, 역시 명쾌한 답안은 아니라서 계속 검토해보는 중이었습니다. 그동안 지러는 D(Phone)번 문제가 쉽다는 걸 깨닫고는 풀기 시작했습니다. 저는 남아있는 문제들을 훑어보며 다음 풀 문제를 찾기 시작했습니다.
E(Tile)번 문제는 그림부터 어쩐지 익숙하였기에, 간단한 DP 문제일 것이라 예상했습니다. 아마 제가 지러에게 E를 좀 봐달란 얘기를 했었던 것 같은데, D번이 더 쉬웠는지 D번을 풀기 시작했습니다. 다음 문제로 대충 E(Tile)를 밀어넣고는 F(Meteor)를 살펴 보았습니다. 아마 곳곳에 가득한 nhn 때문에 문제에 겁을 먹었던 것 같은데, 대충 그림만 보고는 "frame을 어느 위치에 배치해야 가장 많은 meteor를 포함하겠느냐"는 몹시도 어려운 문제라 생각했습니다. I번은 그림이 만만찮아 보여서 넘어갔고, 다음으로 눈에 들어온 문제는 G였습니다.
G(Signals)도 처음에는 그림만 보고 굉장히 어려울 것이라 생각했는데, 처음에는 회로의 저항값을 구하여 두 회로의 저항값을 비교하는 문제라 생각했습니다. 하지만 두 번째 페이지의 =, <, >, 0, 1의 다양한 출력을 보면서 뭔가 이상하다는 느낌을 받았고, 문제를 잘 읽어보니 그렇게 어렵지만은 않은 문제라는 생각이 들었습니다. 관건은 생성될 수 있는 스트링의 수가 최대 얼마까지 가능하냐 였는데, 도맹과 토의하면서 2^10, 3^7을 계산해 보고는 그렇게 크지는 않다는 걸 깨달았습니다. 저는 다음 문제로 G를 잡고 파싱을 어떻게 할지를 생각해 보고 있었습니다.
그동안 지러는 D(Phone)번을 성공적으로 풀었고, 제가 G(Signals)번 문제를 잡고 들어갔습니다. C(Network)번과 G번 중에 어느 것을 먼저 택할지 잠시 고민했는데, C번이 그렇게 깔끔하지는 않을 것 같아서 G번을 먼저 잡게 되었습니다. 그러는 동안 다른 팀원들에게는 E를 주었는데, 대칭인 경우 때문에 생각보다는 까다로운 문제였습니다. 하지만 지러가 문제를 잡고 고민하기 시작했고, 도맹은 대강 C(Network)를 정리하고 H(Puzzle)를 보기 시작했습니다. 비슷한 문제를 접해 본 적이 있었다는 얘기를 하면서, 아마 오토마타를 그리기 시작했던 것 같습니다.
그리고 저는 이때부터 랭킹을 제대로 보기 시작했습니다. 페이스는 상대적으로 썩 나쁜 편은 아니었는데, 그래도 시간이 제법 걸리는 편이었던 것 같습니다. 순위를 대강 살펴보며 마이티 프랜드가 생각보다 강하다는 것과, 그리고 team38이 예상 외의 다크호스라는 느낌이 들었습니다. 아마 아실 분은 아시겠지만, 대회장 왼쪽 주변에 유망한-_-; 팀들이 제법 몰려 있었기 때문에 team38이 의외라는 생각을 했던 것 같습니다.
아무튼 저는 G(Signals)를 잡고 풀기 시작했는데, 파싱에 대해서 깊게 생각해보지 않고 문제를 잡았기 때문에 꽤나 고생했습니다. 두 가지의 섣부른 가정을 했었는데, 하나는 | 연산자가 (괄호 안에서) 최대 하나뿐이라는 것이었고, 다른 하나는 모든 | 연산자들이 괄호 안에만 위치한다는 것이었습니다. 게다가 구현마저도 제대로 되지 않아서, 중간 결과를 찍어보면서 또 패닉 상태에 빠지게 되었습니다. 그동안 지러가 E(Tile)번 문제의 식을 정리했고, 저는 컴퓨터 앞에서 물러나 G번의 현재 코드를 어떻게하면 쉽게 고칠 수 있을지를 고민했습니다.
그동안 도맹은 H(Puzzle)번 문제를 분석하다 말고 F(Meteor)번 문제를 읽고 있었습니다. 제가 굉장히 어려운 문제처럼 팀원들에게 전달했지만, 도맹은 문제를 잘 읽어보고는 제가 생각했던 것 보다는 쉬운 문제라는 걸 알아냈습니다. 다만 조금 까다로운 부분이라면 폐구간이 아니라 개구간들을 다뤄야 한다는 점과, 실수 처리에서 오차가 생길 수 있다는 것이었습니다. 그렇다고는 해도 해법 자체는 그렇게 까다롭지 않았기 때문에, F번 문제보다는 H번 문제에 집중했던 것 같습니다.
한 번 정도의 실수가 있었지만 지러가 E(Tile)번 문제를 잘 풀어 주었고, 저는 그동안 G(Signals)번 코드를 수정하고 있었습니다. 대회 때의 통계를 보니까 이 때의 간격이 제법 길었네요. E번이 53분이었고, G번이 115분이었으니 거의 한시간 정도의 간격이었습니다. 저는 G번 소스를 계속 수정하고 있었고, 그 와중에 지러가 I번 문제를 읽어보고, 풀고 있었습니다. G가 생각만큼 잘 풀리지 않아서 제가 많이 당황했었던 것 같은데, 팀원들이 차분히 하면 될꺼라는 식의 위로를 해 주어 고맙.. 긴 했는데 후배들 앞에서 이게 뭔 추태냔 생각을 했습니다 OTL 그 와중에 잠시 화장실에도 다녀오면서 team38의 정체를 슬쩍 보았는데, 놀랍게도 파키스탄에서 온 팀이었습니다! 뭐 그 때까지는 인도 팀인줄 알았지만..
우여곡절 끝에 제가 G(Signals)번 소스를 완성했고, 2^10과 3^7의 데이터를 넣어 보고는 생각보다 빠른 속도에 만족하여 섭밋을 했습니다. 코드가 복잡한 편이었지만, 그래도 한 번에 AC를 받을 수 있었습니다. 중간 중간에 디버깅하는 짬을 이용하여, 지러는 동시에 I(Turtle)번 문제를 풀고 있었습니다. 중간에 지러가 I번 문제에 대한 질문도 보냈었는데, 그러면서 생각을 잘 정리했는지 I번 문제를 제법 빠르게 풀어냈습니다. 이때까지만 해도 페이스가 굉장히 좋았고, 다른 문제들에 대한 해법도 다 나와있는 상황이었기 때문에, J번 문제를 푸는 팀이 1등을 하거나, 혹은 나머지 아홉 문제에서 페널티 싸움이 일어날 거라는 생각을 했었습니다. 하지만.. Life is not that easy.
이제 남아있는 문제는 C(Network), F(Meteor), H(Puzzle), J(Number). 도맹은 C, F, H를 잡고 있으면서 모든 문제에 대한 해법을 내놓았지만, 다른 팀원들에 밀려 컴퓨터는 잡지 못하는 상황이었습니다 -_-; 다른 문제가 풀리는 동안 지러와 도맹은 C번 문제에 대한 토의를 했고, C번 문제가 복잡한 DP보다는 간단한 그리디로 풀릴 것 같다는 결론을 내렸습니다. 그러면서 C번은 도맹의 손을 떠나 지러에게로 넘어갔습니다. 간단한 해법에 비해 복잡한 numerical 문제 때문에 코딩에 들어가지 못했던 F번은 어느덧 제 손으로 들어와 있었습니다. 어버버버.. 어쩔 수 없이 도맹은 H번에 집중해야 했습니다 ;ㅅ;
아마 다음 문제는 C(Network)번이었던 것 같은데, 지러가 이런저런 생각을 하면서 코딩에 들어갔습니다. 그동안 저는 F(Meteor)번 문제를 준비하고 있었습니다. 어차피 문제에서 나오게 되는 계산식들은 모두 정수 계수였기 때문에, 저는 시간을 실수형이 아니라 유리수(fraction)로 표현해야 겠다는 생각을 했습니다. 준비해간 frac class를 꺼내들고(thanks to JM!) F번 문제에서 사용되는 식들을 정리하고 있었습니다. 그동안 지러는 C번은 풀었지만 WA를 받았고, 소스 코드를 인쇄하여 디버깅을 시작했습니다. 그리고 저는 F번 문제를 들어갔습니다.
일단은 준비해 간 frac class를 열심히 따라 치고 있었는데, 나중에 생각해보니 결국 필요한 건 비교 연산자 뿐이었습니다 -_- 준비해 간 코드에서는 비교 연산자가 빠져 있었는데, set이나 map에서 사용할 수 있도록 적당히 순서만 주어져 있는 비교 연산자였습니다. 하여, 저는 팀원들에게 비교 연산자 부분을 채워달라는 부탁을 하면서 F번을 풀고 있었습니다. 약간 까다로운 면이 있기는 했지만 팀원들이 금방 정리를 해 주었고, 덕분에 별 어려움 없이 frac class를 완성할 수 있었습니다.
F(Meteor) 문제에서 제일 까다로운 부분은 시간을 구하는 부분이었는데, 도맹이 x축과 만나는 시간과 y축과 만나는 시간의 intersection을 구하면 될 것 같다는 조언을 해 주었습니다. Frac class를 사용했기 때문에 intersection을 구하는 게 조금 까다롭기는 했지만, 어쨌든 소스 코드를 완성할 수 있었습니다. 하지만 예제를 넣어보니 런타임 에러가 났고, gcd 함수에서 0으로 나누는 실수가 있었던 기억 때문에 gcd 함수를 적당히 수정했습니다. 그리고 섭밋했지만, 결과는 WA였습니다. 결국 저도 지러처럼 소스코드를 인쇄해서 디버깅을 시작했습니다.
그동안 도맹은 대망의 H(Puzzle)를 코딩하기 시작했습니다. H는 구현이 까다로운 문제였기 때문에 시간이 많이 걸릴 것 같았습니다. 그래서 중간에 저와 지러가 잠깐씩 컴퓨터를 잡아가며 자신의 소스를 돌려보고, 수정하는 식으로 가닥을 잡았습니다.
제가 한 실수는 속도 벡터의 한 축 성분이 0일 경우였습니다. 이 경우에는 유성이 한 쪽 축에 평행하게 이동하기 때문에, 다른 축 방향으로는 사각형과 교점이 생기지 않습니다. 뒤늦게 이 실수를 발견하고 수정했지만, 결과는 다시 WA. 이런저런 케이스를 넣어 보다가, 예제도 안 나오는 솔루션이라는 걸 알게 되었습니다. 다시 또 패닉 상태에 빠져들면서 한참 소스코드를 들여다봤고, 속도 벡터의 한 성분이 0일 경우에 무조건 사각형과 교차한다고 짠 것을 발견하게 되었습니다. 이 부분을 수정하여 다시 제출한 끝에 AC를 받을 수 있었습니다.
도맹은 여전히 H를 풀고 있었고, 저는 지러의 코드를 같이 살펴 보았습니다. 지러는 특이하게도 stack을 사용하여 non-recursive한 구현을 사용했는데, 제게는 익숙하지 않은 방식이었기 때문에 소스 코드를 이해하기 어려웠습니다. 그래서 일단 방법에 대한 토의를 시작했는데, 이 때에도 같은 내용을 서로 다른 식으로 표현하는 바람에 의사소통이 어려웠습니다. 그러다보니 자꾸 시간만 흐르게 되었고, 여전히 C는 풀리지 않은 상태였습니다. 그 와중에 도맹은 H번의 코드를 완성했지만, 예제가 잘 나오지 않았습니다. 답 자체는 맞게 구하는데, 초기화에 이상이 있는지 입력 순서를 바꾸면 잘못된 답이 나오기도 했습니다. 아홉 문제에서 열 문제를 사이에서 1등이 나올 거라는 생각을, 이 때부터 부정하게 되었습니다.
한 시간 정도가 남은 시점이었기 때문에, 이 무렵에 팀원 사이에 약간의 갈등이 생겼습니다. 두 문제를 잡을 것인지, 아니면 한 문제에 집중할 것인지. H번은 전적으로 도맹의 손에 달린 문제였고, C번 문제의 알고리즘이 그렇게 복잡한 것은 아니었기 때문에, 제가 recursive하게 C번을 다시 구현해 보는 쪽으로 가닥을 잡았습니다. 그 와중에 도맹은 H번 소스를 완성했는데, 결과는 TLE였습니다. 제가 C번을 다시 짜는 동안 도맹과 지러는 H번 소스를 같이 살펴보면서, 속도가 개선될만한 부분을 고쳐다가기 시작했습니다.
하지만 저 역시 C번을 깊게 생각해 본 것은 아니었기 때문에 조금씩 꼬이게 되었습니다. 도맹은 H번 소스를 조금씩 고쳐봤지만, 결과는 여전히 TLE 였습니다. 정신없기 고치다보니 아마 중간에는 예제도 안 나오는 상황이 있었던 것 같습니다. 방법 자체는 맞다는 생각을 했고, 이론적으로는 시간 내에 나올법한 시간복잡도 였기 때문에, 섭밋 자체를 여러 번 시도했었습니다. 똑같은 코드를 VS로도 섭밋해보고, g++로도 섭밋을 해 봤는데, 이 와중에 제출을 잘못하여 몇 번 컴파일 에러를 받기도 했습니다.
H번이 여러 번의 시도에도 불구하고 TLE였기 때문에, 저와 지러가 함께 제 C번 소스를 보는 쪽으로 가닥을 잡게 되었습니다. 물론 도맹은 옆에서 계속 H번을 개선할 방법을 찾고 있었고요. 알고리즘부터 명확히 해야겠다는 생각에, 지러에게 제가 구현하려 하는 방식을 이야기 해 주었습니다. 그리고 이게 저희 팀의 승리 비결이었습니다. 제가 무심코 말한 한 마디에 지러가 자신의 실수를 발견했거든요. "아 맞다, leaf만 고려하는 거구나!"
이 때문에 다시 또 갈등에 빠졌는데, 지러의 기존 C번 소스를 잘 수정하는 쪽으로 방향을 잡았습니다. 그 와중에 도맹도 H번을 최대한 고쳐 보기로 하고요. 하지만 H번 보다는 C번이 쉬웠기 때문에, 저는 개인적으로 C번을 잡는 쪽으로 생각을 했었습니다. 도맹은 H번 최후의 수정안을 내놓았고, 소스를 수정하여 섭밋했습니다. 슬슬 페널티가 큰 의미없는 시점이 되었기에, 똑같은 코드를 바로 g++로도 섭밋했습니다. 그리고 결과도 보지 않고, C번에 집중하기로 하였습니다. 이 때가 대회 종료 10여분 전이었습니다.
제가 지러의 C번 소스코드를 제대로 읽었더라면 좋았겠지만, 대충만 봤었기 때문에 결국 지러가 혼자서 자신의 코드를 수정해야 했습니다. 지러는 이것 저것 수정을 시작했고, 중간중간에 컴파일도 해보고 테스트도 해봤습니다. 지러는 일단 예제가 나오는 단계가 되면 무조건 섭밋을 하자고 했고, 저 역시 그게 좋겠다는 생각이 들었습니다.
다들 비슷하겠지만, 이 마지막 몇 분이 정말 피말리는 순간이었습니다. 지러는 계속 소스코드를 수정하고, 섭밋하고, 다시 수정하고, 수정하다 말고 아까 섭밋한 결과를 보고, 계속해서 수정하고, 섭밋하고, 다시 수정하는 과정을 계속 반복하고 있었습니다. 그 와중에 저 앞쪽에서 극적인 AC를 받았는지, 제법 큰 환호성이 들려왔습니다. 사진 기사분들이 바로 그 곳으로 달려갔지만, 저희는 그쪽에 신경 쓸 겨를도 없었습니다. 두 번짼가 세 번짼가의 섭밋을 마치고, 다시 소스 코드를 수정하고 있었을 때 극적인 메시지가 날아왔습니다. Yes - Accepted! 종료를 3분 앞두고 날아온 이 메시지에 저희 팀도 환호성을 지를 수밖에 없었습니다 ^^;
Team38이 매우 잘 하고 있었고, 앞쪽에서 들려온 환호성의 정체를 알 수 없었지만, 그래도 초반에 페널티를 많이 아낀 덕분에 2위 정도는 할 수 있으리란 예감이 들었습니다. 남은 3분동안 H번의 수정에 몰두해야 겠지만 저와 지러는 완전 탈력 상태였고, 도맹도 패닉 상태에서 빠져나오기 못했기 때문에 3분은 아무 일 없이 흘러갔습니다. 괜히 환호성을 지른 덕분에 사진 기사분들이 달려오셨고, 저희는 너무 부끄러워 절로 범죄자처럼 고개를 숙이게 되었습니다 ㅡ,.ㅡ
이렇게 대회가 끝나고- 저는 또 개인적인 사정 때문에 잠시 자리를 비우게 되었습니다 -_-; 한참 자리를 비웠다가 대회장으로 돌아왔고, 아마 시상식이 시작되기 30분 전이었던 것 같습니다. 다시 학교 사람들과 만나서 대회에 대한 이야기를 시작했고, 예상 외로 잘했던 team38에 대한 이야기를 꺼냈습니다. 그리고 저는 그 때 처음 team38이 실격 처리 되었다는 얘기를 들었습니다;; 또 지러가 대회가 종료된 후 ZSU 학생이 와서 페널티가 1152을 넘냐고 물어봤다는 얘기를 해 주었습니다. 이런저런 얘기들 때문에- 솔직히 말하자면 이 때부터 1등을 했다는 생각이 들기 시작했습니다 ^^;
그리고 시상식- 뒤늦게 대회장으로 달려오신 JM 님께서, 그리고 다른 OB 분들게서 Tribute 세레모니를 하라는 압박을 주셨지만- 차마 할 수는 없었습니다 -_-a 그리고 마침내 Grand prize 시상- 4년만에 처음으로 올라보는 자리였지만- 어떤 느낌인지는 잘 기억나지 않네요. 그저 그 위에서 찍힌 짤방 소스 하나만이 남은 것 같습니다.
축하해주신 모든 분들께 이 자리를 빌어 감사의 말씀을 드립니다. 모든 분들의 이름을 적고 싶지만, 대회장에서 박수를 쳐 주셨던 모든 분들의 이름을 다 적을 수는 없기에 생략 하겠습니다 ^^; 그리고 대회장에 못 오셨지만, 알고스팟에서 계속 응원해 주셨던 분들께도 감사의 말씀을 드립니다.
아직 올해의 대회는 끝나지 않았습니다-! 지역 대회들도 이제 시작일 뿐이고, 아직 접수도 안 끝난 대회들도 많이 있는 걸로 알고 있습니다. 아무쪼록 많은 분들께서 남은 대회들에 도전하시고, 좋은 결과를 거두셨으면 좋겠습니다. 그래서 올해 월드파이널에서 많은 한국 분들을 만날 수 있기를 희망하며, 혹 그렇지 못하더라도 다들 만족할만한 결과를 거두시기를 희망합니다.
그리고, 끝으로 이 긴 글을 읽어주신 분들께 또 감사의 말씀을 드립니다 ㅎㅎ
17년 전