이른바 명예로운 자가격리를 당한 거지

2020년 회고

강변북로 사진
서강대교에서 찍은 강변북로

올해부터 블로그에 1년 단위로 뭘 했는지 회고를 써 보기로 했습니다. 나중에 잊어버리면 슬프잖아요.

애초에 애인도 친구도 없어서 약속 없는 핑계를 코로나 때문이라고 둘러댈 수 있는 좋은 한 해였습니다. 이른바 명예로운 자가격리를 당한 거죠. 저희 집에서 가장 가까운 박쥐 동굴 방향으로 하루에 세 번 절하고 있습니다.

그래도 코로나가 12월에는 끝나있을 줄 알았는데 그것도 아니더라구요. 일일 확진자 수 최고치 연일 갱신 중이고 저는 집에서 재택근무를 하고 있습니다. 엇 그래도 이제 직장은 있군요 다행이에요

소프트웨어 개발

solved.ac

이미지
살아남았다

올해는 solved.ac 개발에 꽤 집중했습니다. 올해 솔브드에 새로 생긴 큼직한 기능들은 다음과 같습니다.

  • 문제 정렬. (2월) 문제 목록에서 문제 순서를 쉽게 정렬할 수 있습니다.
  • 고급 검색. (2월) 문제 제목뿐 아니라, 맞은 사람 수, 문제 레벨, 내가 푼 문제 등에 필터를 걸 수 있고, AND, OR 등의 연산자를 이용해 검색하는 것이 가능해졌습니다. 검색 쿼리 문법을 직접 만들고, 이를 해석하는 파서 등도 만들었습니다.
  • 프로필 배경. (2월) 프로필을 배경으로 꾸밀 수 있게 되었습니다.
  • 라이벌. (4월) 다른 유저를 라이벌로 등록하고, 라이벌들과 나의 순위를 볼 수 있습니다.
  • 프로필 사진. (5월) 프로필 사진을 올릴 수 있게 되었습니다.
  • 화이트리스트제 종료 및 베타 종료. (6월 5일) 지금은 상상하기 힘들지만, 6월 이전에는 솔브드를 사용하려면 제게 이메일을 보내서 화이트리스트에 등록되어야 했습니다. 6월부터는 기존처럼 크롤링 및 파싱을 하는 방식에서 BOJ에서 유저 정보를 직접 받아오는 방식으로 바뀌어서, 누구나 사이트를 사용할 수 있게 되었습니다.
  • 코드 교환. (8월) 리딤 코드를 입력해 배경과 프로필 뱃지 등을 교환할 수 있게 되었습니다.
  • 자동 갱신. (11월) 이것도 지금은 상상하기 힘들지만, 11월 이전에는 문제를 풀고 나서 프로필에 있는 ‘갱신’ 버튼을 직접 눌러줘야 했습니다. 지금은 문제를 푸는 즉시 경험치가 반영되기 때문에 굳이 갱신 버튼을 누를 필요가 없습니다.

이제 BOJ와의 통합 작업도 거의 마무리되어 solved.ac에 굳이 들어오지 않고도 BOJ에서 solved.ac 문제 난이도와 태그 정보를 확인할 수 있게 되었습니다. 또한, 길라잡이 등의 새로운 기능들이 공개를 앞두고 있습니다. 내년에도 잘 부탁드려요!

Javascript 프레임워크의 세계로

solved.ac의 소스 코드는 원래 대부분 PHP로 되어 있었습니다. 프론트엔드 코드가 복잡해지면서 유지보수에 무리가 갔고, PHP의 특징적인 여러 요소들 때문에 코드 베이스가 PHP인 이상 추가적인 무언가를 개발하기는 힘들겠다 싶었습니다.

그래서 컴포넌트를 만들고 재사용하기 좋다고 들은 React로 무작정 프론트엔드 포팅을 시작했습니다. 검색엔진 최적화도 해야 해서 SSR을 지원해 주는 Next.js도 같이 사용했고요.

당시에는 리액트에 대해 하나도 몰랐지만 지금은 현업에서 매일 사용하고 있을 정도로 익숙해지기 쉬운 프레임워크라고 느낍니다. 진작에 이걸 왜 안 했지? 싶을 정도로요. 참고로 리액트를 쓰기 이전에는 제가 만들었던 사이트들은 아무 프레임워크도 사용하지 않고 오직 vanilla JS로만 돌아갔습니다.

이후에는 Typescript도 적용해 프로젝트를 리팩터했습니다. 타입 추론이 상당한 개발 효율을 가져다주는 것을 체감할 수 있었습니다. 가령 기존에는 API response에 대한 타입 정의가 없어서(게으르게도 명세도 딱히 해 두지 않았었습니다), 필드나 타입을 헷갈려서 오류가 발생하거나 개발 속도가 느렸었는데, 지금은 타입 정의를 열심히 만들어서 제 human error로 오류가 발생할 수 있는 가능성을 대폭 줄였습니다. 되게 기본적인 거지만 나름 뿌듯했던 경험이라 소스 코드를 첨부해 공유해 봅니다.

type SolvedPageContext = NextPageContext & GlobalProps

export type SolvedPage<
  P = { siteTitle?: string[] },
  IP = P
> = NextComponentType<
  SolvedPageContext,
  IP & { siteTitle?: string[] },
  P & { siteTitle?: string[] }
>

export default SolvedPageContext

이렇게 정의하면 페이지의 Props의 타입 정의가 어떤지에 따라 getInitialProps에서 반환해 줘야 하는 타입이 결정됩니다. 아래 페이지의 경우 props.result의 타입이 Problem[]으로 정해져 있기 때문에 ProblemList에서 렌더 로직을 짜기가 상당히 편해집니다. 에디터 연동은 두말할 것도 없구요.

type Props = SolvedApiResponse<SearchProblems>

const LevelProblems: SolvedPage<Props> = (props) => {
  // ...
  return (
    <PageLayout>
      <h1>
        <TierMark value={level} locked={false} showName={true} />
        {caption ? ` — ${caption}` : null}
      </h1>
      <ProblemSortController
        currentSort={newQuery.sort}
        currentDirection={newQuery.direction}
      />
      <div>
        <ProblemList
          result={props.result}
          page={page}
          keys={['id', 'title', 'solved_count', 'average_try']}
        />
      </div>
    </PageLayout>
  )
}

LevelProblems.getInitialProps = async (ctx) => {
  // ...
  try {
    const response = await SolvedApi<SearchProblems>(token).get(
      '/search/problems.json',
      {
        params: {
          query: queryString,
          page: +(page ?? 1),
          sort: sort ?? ctx.currentSettings?.problem_sort_by,
          sort_direction:
            direction ?? ctx.currentSettings?.problem_sort_direction,
        },
      }
    )
    return {
      siteTitle: ['문제', '레벨', levelName(+(level ?? 0))],
      ...response.data,
    }
  } catch (e) {
    // ...
  }
}

여하튼 이런 식으로 기존에 PHP로 짜여 있었던 프론트엔드를 몇 달에 걸쳐 전부 React + Typescript + Next.js로 다시 짰습니다.

안타깝게도 백엔드는 아직도 PHP로 되어 있습니다. 현재는 길라잡이를 만들면서 백엔드도 Node + Express로 다시 구현하려는 계획을 갖고 있습니다. 완료되면 solved.ac에서 PHP 코드를 찾아보기 힘들게 되겠네요.

취업

넥하

위의 개발 경험 덕분에 6월 중순부터 넥슨컴퍼니 산하의 엔진스튜디오에서 일하게 되었습니다. 회고를 쓰고 있는 지금 시점으로 입사한 지 벌써 반 년이 넘었네요.

게임회사지만 제 주 업무는 웹 프론트엔드 엔지니어입니다. 사내외 여러 서비스의 개발을 맡아 하고 있습니다. 어떤 서비스라고 말하긴 그렇지만 아마 이 글을 읽고 계신 분들 중 적지 않은 분들께서 제가 작업했던 서비스를 이미 사용해 보셨을 겁니다. 😉

이미지
서버는 오로라

여담으로, 회사에서 매달 넥슨캐시를 일정량 지급하는데 쓸 데가 딱히 없어서 메이플스토리를 시작했습니다. 이제 모라스 코앞이네요. 12월 30일에 모라스에 갔습니다. 어쩌다 보니 받는 것보다 많이 결제하고 있지만 결제한 만큼 월급으로 돌려(?)주니 괜찮지 않을까라고 생각하고 있어요..


대회 프로그래밍

코드포스

올해는 그토록 염원하던 오렌지를 드디어 갔습니다.

2100

근데 레이팅이 딱 2100점입니다.

근데 이 어려운 걸 한 번 더 해냅니다.

+0

이 사람은 대체 뭐 하는 사람일까요? 대체 어떻게 했던 걸까요?

오렌지 수문장

그래프가 딱 2100에 걸쳐 있습니다.

오렌지가 되니까 Div 1 이외에서는 레이팅 변동이 일어나지 않습니다. 근데 Div 1은 자주 열리지도 않고, 열리는 날에는 제가 뭔가 바빠서 매번 못 치게 되더랍니다. 뭐 한마디로 게을러진 거죠. 복학할 때 적의환향하겠다던 시프트는 대체 어디로 가고? 다시 열심히 쳐야겠어요.

대회 개최

이미지
보셨나요? 저의 15 연속 검수!

UCPC 2020을 개최하고, 많은 대회에서 검수 및 출제를 했습니다.

  • 진짜 최종 구데기컵 2 – 출제, 검수
  • UCPC 2020 (전대프연 여름 대회) – 운영 총괄, 출제, 검수, 조판
  • SUAPC 2020 (신촌지역 연합 여름 대회) – 출제, 검수, 조판
  • 신촌지역 연합 캠프 2020 – 검수
  • SNUPC 2020 (서울대학교) – 검수
  • Uni-CODE 2020 (UNIST) – 검수, 조판
  • SPC 2020 (서강대학교) – 검수, 조판

알고리즘 문제 제작과 검수, 프로그래밍 대회 진행 프로세스에 대해 과도할 정도로 많은 것들을 배울 수 있었던 한 해였습니다. 특히 글을 잘 쓰는 법을 배울 수 있었어요. 기회가 되면 SUAPC/SPC 출제/검수 후기도 작성해 보고 싶습니다.

대회 참가

여러 대회에 참가했으나 올해는 다소 부진했습니다.

  • ICPC Asia-Seoul 2020 National First Round – 팀 Redshift로 출전, 5/12솔브 #36
    • 풀이를 옳게 생각했던 건 3문제나 더 있었는데.. 강의실 키보드 탓을 했습니다.
  • SCPC 2020 – Finalist, 172/1000
    • 4번 문제가 되게 풀릴 거 같이 생겼길래 3시간동안 4번 문제만 잡다가 결국 못 풀고 망했습니다. 점수를 긁읍시다 여러분.

팀 연습 때는 NxxRC 8~10솔브 밀고 다녀서 잘하겠지 싶었는데 막상 한국 예선에서 저런 성적이 나와서 당황스러웠습니다. 개인 연습 부족인 거 같네요.

…적의환향 할 수 있을까요?

스터디 진행

Sogang ICPC Team에서 연초에 아래 주제들을 다루는 스터디를 진행했습니다.


그래픽 디자인

많은 프로그래밍 대회 운영에 참가하면서, 자연스럽게도 관련 포스터를 만들게 됐습니다.


프로그래밍 대회 문제들에 들어가는 그래픽도 많이 만들었습니다.


solved.ac 공식 SNS 계정이 생기면서 정사각형 모양의 그래픽도 만들었습니다.

solved.ac 웹 디자인은 포토샵이나 일러스트를 켜서 하지 않고 CSS로 직접 하기 때문에 따로 목업 디자인 같은 건 따로 없습니다.

아쉽게도 예전처럼 개인작을 만들거나 할 여유는 없었네요.


이외에

휴학하고 회사 다니면서 아무것도 안 한 줄 알았는데 정리해 보니까 뭔가 한 게 꽤 있었네요. 건강검진에서 많이 쉬라는 소리를 들어서 내년 목표는 바쁘지 않게 한 해를 보내는 것으로 해야겠습니다.

이미지
갤럭시 노트20 울트라 5G

휴대폰을 갤럭시 노트 20으로 바꿨어요. 흰색이 정말 예쁘길래 흰색으로 샀는데 잘한 선택인 거 같아요. 5G폰이지만 5G가 너무 안 터진다는 소리가 많아서 5G를 쓰지 않기 위해 자급제로 샀습니다. 근데 정작 노트에서 가장 중요한 기능이라고 할 수 있는 S펜은 자주 안 쓰게 되더라구요. 왼쪽에 달려 있어서 그런가..

라프텔

저어어엉말 오랜만에 애니메이션을 열심히 봤습니다. 넷플릭스도 열심히 봤습니다. 베스 하먼을 보고 체스 세트를 샀어요. 근데 잘 못 두는 거 같네요.

이미지
신도림역 출발열차 (특징: 신도림에서 출발함)

신도림역 출발열차의 존재를 알고 삶의 질이 올라갔어요.

이미지
츄니즘

세가의 리듬게임 ‘츄니즘’이 한국에 정식 발매되어서 휴가 쓰고 한주 내내 다녀왔어요.

이미지
서강대교에서 찍은 여의도

자전거를 유난히 많이 탔던 해였던 거 같기도 하네요. 특히 학교에서 놀거나 문제 풀다가 or 상수역 오락실에서 놀다가 서강대교 건너서 집에 따릉이 타고 자주 왔는데, 이제는 코로나 시국이 심해져서 자전거 타고 밖에 돌아다니기도 눈치 보이게 되었어요. 코로나가 없던 시절이 너무 그립네요..


솔브드 백엔드 Node.js 포팅도 마치고 싶었고, 그림도 그릴 줄 알게 되면 좋겠다 싶었고, 코드포스 레드도 가고 싶었고, 되게 하고 싶었던 게 많았어요. 다 못 이뤄서 올해 별로 한 게 없었던 줄 알았는데.. 정리해 보니까 이런 시국 속에서도 올해 되게 뭔가 많이 했었네요 😅 제가 너무 하고 싶은 게 많았나봐요. 좀 한가하게 살아도 되겠습니다. 건강검진에서도 많이 쉬라는 소리를 들었구요.

이 정도면 올해 나름 잘 보냈네요. 신년 목표도 한 해 적당히 잘 보내는 걸로 해야겠습니다. 새해 복 많이 받으세요~ 🔔

UCPC 2020을 개최했습니다

UCPC 2020 개최 후기

기회가 되어 올해 전국 대학생 프로그래밍 대회 동아리 연합전대프연 회장을 맡아 UCPC 2020을 개최했습니다. 2019 서강대학교 프로그래밍 대회에 이은 두 번째 대회 운영입니다.

전대프연은 알고리즘 문제해결을 좋아하는 25개 대학교의 26개 동아리의 연합이고, UCPC는 전대프연에서 매년 개최하는 프로그래밍 대회입니다. 보통 가을에 열리는 국제 대학생 프로그래밍 경시대회 한국 리저널의 연습 격 대회로 시작해서, 지금은 국내에서 열리는 프로그래밍 대회 중에서 커뮤니티 대회로는 가장 큰 규모를 갖고 있습니다.

왜 하겠다고 했나요?

대회를 코디네이트하는 건 힘듭니다. 그래서 선뜻 총대를 매겠다고 나서는 경우는 드물고, 실제로 3년 전엔 대회가 안 열린 적도 있었습니다. 4월이 되어도 아무도 대회 총괄을 하려는 사람이 없어 올해는 제가 열어보겠다고 했습니다. 저는 당시 휴학 무직 백수로 즐겁게 하루종일 뒹굴뒹굴 감자칩 먹으면서 매일 컴퓨터랑 눈싸움이나 하고 있었는데, 아마 휴학하고 있지 않았다면 상당히 바빴을 테니 나서지도 않지 않았을까 싶습니다.

인수인계를 받자마자 대회 기획을 시작하고, 출제자를 모집했습니다. 그러나 올해는 큰 문제가 있었습니다. 경우에 따라 기획의 근간이 바뀔 수도 있는 아주 큰 문제였습니다. 바로..

준비되지 않은 우리 앞에 성큼 다가와 버린 코로나 시대

코로나 큐

2020년을 살아가는 여러분께서는 모두 마음 속에 우선순위 큐 하나쯤을 갖고 계실 겁니다. 바로 코로나 시대가 끝나면 할 일이라는 이름의 우선순위 큐입니다. 저는 벌써 원소가 120개를 넘어가려고 합니다. 코로나 끝나면 여행 가야지, 코로나 끝나면 맛집 탐방 다녀야지, 코로나 끝나면 못 봤던 친구들 만나서 밥 먹어야지, … 분명 4월즈음에만 해도 큐가 이렇게 커질 거라고는 생각하지 못했습니다. 상황이 괜찮아질 줄 알았으니까요.

UCPC도 마찬가지였습니다. 대회가 열릴 7월즈음에는 상황이 괜찮아지겠지-라는 막연한 기대가 있었습니다. 그래서 오프라인으로 대회를 진행하려고 계획했습니다. 오프라인으로 개최하는 것이 좋은 이유를 나열해 보자면:

  • UCPC는 프로그래밍 대회 동아리 회원들의 교류와 경쟁의 장을 목표로 하고 있습니다.
  • 부정행위 검사가 힘듭니다. 참가자가 다른 팀 코드를 베꼈는지는 쉽게 알 수 있습니다. 하지만, 예를 들어, 3인 팀 대회인 UCPC에서 4인 이상의 팀원이 코드를 작성한다던가, (진짜 정말 극단적인 예시로) 대회 중에 tourist에게 문제 해법을 물어보고 이를 이용해 해결하는 등의 부정행위는 막기 힘드며 검사하는 것도 거의 불가능에 가깝습니다.
  • UCPC와 같은 규모의 대회에는 운영비가 많이 필요합니다. 그래서 스폰서의 도움이 절실합니다. 하지만 온라인으로 진행할 경우 오프라인으로 진행하는 것보다 스폰서에게 후원에 대한 메리트를 어떻게 줄 수 있을지 생각해내고 이를 어필하는 것이 상대적으로 어렵습니다.

반면 온라인으로 개최하는 것도 나름의 장점이 있습니다.

  • 예산에서 대관료를 고려할 필요가 없어집니다.
  • 대회장에 오지 않아도 됩니다. 지방에서 서울로 올라와서 참가하시는 분들의 경우 대회 참여를 위해 만 하루를 잡고 참가하시는 경우가 흔하다고 들었습니다. 이런 모든 비용을 절감할 수 있습니다.

분명 온라인 대회로 진행해서 얻을 수 있는 장점들도 있지만, 오프라인 대회로 개최하는 것의 장점이 훨씬 크리티컬했기에 할 수만 있다면 오프라인으로 진행하고 싶었습니다.

그러나 모두가 아시는 대로…

…하루 10명대였던 확진 판정은 갑자기 다시 30명, 40명, 80명이 되었고, 오프라인 대회로 개최할 경우 스태프가 방역을 책임질 수 없겠다는 생각이 들어 오프라인 개최를 포기하게 되었습니다.

정작 이 글을 쓰고 있는 시점에서는 일일 확진자가 100명 넘게 발생하고 있네요.

온라인이기 때문에 할 수 있는 것을 하기

그래서 할 수 있는 거라도 최대한 재밌게 해 보기로 했습니다. 일단 위에서 오프라인 대회의 장점으로 언급되었던 것들을 온라인으로 어떻게 살릴 수 있을지에 대해 먼저 고민해 봤습니다.

  • 교류와 경쟁의 장 제공 – 기존에 대회장에서 하던 풀이 방송과 스코어보드 방송을 Twitch 온라인 방송을 통해 진행하기로 합니다. 물리적인 공간보다는 덜하겠지만, 실시간 댓글을 통해 교류할 수 있게 됩니다.
  • 스폰서 어필 – 온라인 방송 중 방송 화면의 공간을 일부 활용해 스폰서 로고를 지속적으로 노출합니다. 스튜디오가 구해진다면 후원 세션을 참가자들이 가장 많이 볼 시간대인 대회 종료 직후 ~ 문제 해설 전에 진행합니다.

부정행위에 대한 고민이 가장 많았습니다. 대략 다음과 같은 의식의 흐름을 거쳤습니다.

  • 웹캠이 있으면 부정행위를 막을 수 있을까?
    • 웹캠이 있다고 해도 스크린에 카카오톡 같은 걸 띄우면 충분히 부정행위를 할 수 있지 않을까? 그럼 스크린 녹화도 필요할까?
    • 스크린 녹화를 하더라도 다른 장비가 있으면 충분히 부정행위를 할 수 있지 않을까?
    • 애초에 참가팀 50팀이 동시 접속할 수 있는 화상 채팅 서비스가 존재할까?
    • 존재한다고 하든 안 하든 우리가 그 화면을 전부 모니터링할 수 있는 여력이 있을까?
  • 웹캠은 힘들 것 같으니 카피 체크를 하자
    • 팀 간의 복붙은 막을 수 있겠지만 4명 이상이 한 팀으로 친 경우는 어떻게 막을 수 있을까?
    • 4명 이상이 한 팀으로 치지 않았더라도, 예를 들어 누군가 대회 중에 대회 문제를 random Codeforces red한테 물어봐서 푼다면? 어떻게 막을 수 있을까?
    • 그 코드는 언제 다 읽을까?
  • 결국 어떻게 해도 부정행위를 막을 수는 없는 거 아닌가?
  • 근데 어차피 부정행위 해서 높은 점수 받고 못 보던 사람이 스코어보드 위에 있는 상황이 발생하면 다 티나지 않을까?

결론은 PS 커뮤니티를 믿고 코드 카피 체크 이외의 별도의 부정행위 검사를 하지 않는 것으로 났습니다. 커뮤니티 대회였기에 가능했던 결정이었습니다. 큰 상금이 걸린 기업 대회였다면 이런 상황에서 상당히 곤란했을 것입니다.

인터넷 방송

Twitch Sports: Getting Started | Twitch Blog
인터넷 방송 플랫폼 트위치

그렇게 결정하고 나니 인터넷 방송이 대회 기획에서 출제와 검수를 제외한 가장 중요한 요소가 되었습니다. 프로그래밍 대회에 많이 참여해 보셨고 (무려 ICPC 월드 파이널!) 관련 스트림도 자주 하시는, 세계적인 인기를 자랑하는 월클(World Class) 스타 정재헌Gravekper님께 참여해주실 수 있는지 여쭸고, 흔쾌히 승낙해 주셔서 함께 온라인 방송을 기획하게 되었습니다.

방송에서 뭘 다루면 좋을지, 방송을 언제부터 켜면 좋을지, 화면은 어떻게 구성하면 좋을지 등등을 대회 직전까지 열심히 논의했습니다. 인터넷 방송에는 문제 풀이, 스코어보드, 후원 세션은 필수로 들어가야 했고, 이외에 대회에 참여하는 사람들도, 참여하지 않는 사람들도 모두 볼 수 있다는 점을 감안하면서 대회 종료 전에 뭘 하면 좋을까를 고민했습니다. 이에 온라인 방송이 있는 다른 대회들을 참고했습니다.

  • ICPCIOI는 대회 중에 실시간 스코어보드와 문제 해설을 보여줍니다. ICPC 방송은 생각보다 재밌습니다. 그래서 사실 제가 하고 싶었던 이상에 가까웠던 방송 형태이긴 하나, 온라인 대회이고 웹캠 사용을 포기했기 때문에 화면에 보여줄 수 있는 게 없었고, UCPC 방송은 대회 참가자들도 볼 수 있는 방송이었기에 문제 해설도 보여줄 수 없었습니다.
  • 반면 Google Hash Code 방송은 참가자들이 대회 중에 시청할 수 있습니다. 그래서 대회 시작 전후에만 방송을 하고, 대회 중에는 아무것도 보여주지 않습니다.

하지만 대회 중에 아무것도 보여주지 않으면 심심하기 때문에 간단히 스코어보드를 띄워 두기로 결정했습니다.

문제 풀이와 후원 세션도 걱정이었습니다. 지금까지 UCPC 풀이는 출제자가 강단에 나와 슬라이드를 보여주는 식으로 진행했고, 후원 세션도 마찬가지였습니다. 근데 올해는 어쩌죠?

다행히 감사하게도 작년에 이어 고려대학교 SW중심대학사업단에서 장소를 제공해 주셔서, 고려대학교 정보대학의 대형 강의실 하나를 스튜디오로 사용할 수 있었습니다. 기존처럼 출제진이 강단에서 슬라이드로 발표하고 이를 카메라로 찍어 방송할 수 있었습니다.

출제와 검수

코로나 시대가 되어도 프로그래밍 대회에서 변하지 않는 것은 바로 출제와 검수입니다. 올해는 제가 여름에 회사에 갈 걸 우려해 대회 준비를 일찍 시작했습니다. 출제진은 우리나라 최대의 알고리즘 문제해결 커뮤니티인 BOJ Slack에서 모집했고, 이후에 추가로 call for tasks를 통해 문제 공모를 받았습니다. 올해는 공모받은 문제가 많아 출제진 풀이 상당히 컸습니다.

슬랙

기획을 포함해 모든 소통은 Slack으로 진행했습니다. Slack의 (무료 버전의) 최대 단점은 메시지가 10,000개 이상 쌓이면 이전 메시지들은 하나씩 못 보게 된다는 것인데요, 마침 코로나로 인해 Slack에서 3달간 무료로 Standard Plan을 제공해 주고 있어서 고민 없이 사용했습니다.

문제마다 하나의 채널을 만들어 최대한 맥락을 잃지 않고 대화할 수 있도록 했습니다. 또한 대회 전반적인 공지를 올릴 #general 채널, 인터넷 방송을 기획할 #broadcasting 채널, 컨테스트 도중 생기는 라이브 이슈에 대응할 #contest-finals / #contest-preliminaries 채널 등을 필요에 따라 주제 단위로 만들어 활용했습니다.

Zapier

특히 박수찬tncks0121님의 도움을 받아, Slack과 Zapier 연동을 사용해 call for tasks로 메일을 받으면 메일 내용을 자동으로 Slack 채널에 포워딩해 주도록 설정할 수 있었습니다. 다만 Zapier의 문제인지 후술할 UCPC 메일서버 문제인지는 모르겠지만 메일이 몇 개 누락되는 경우가 생겼습니다. 이런 경우엔 제가 UCPC 메일함에서 직접 공모받은 문제를 출제 채널에 배달해야 했습니다.

구글 드라이브

출제/검수 현황과 문제 자료 관리 등에는 Google Drive를 사용했습니다. G Suite에 제공되는 기능인 공유 드라이브를 적극적으로 활용했습니다.

출제

대회 분위기 정하기

출제 초기에는 예선과 본선 각각의 적절한 난이도 커브를 결정해 주는 것이 중요합니다. 문제들이 너무 쉬우면 대회 중에 문제를 전부 해결하는 팀은 대회가 끝날 때까지 할 게 없어서 심심해지고, 반대로 너무 어려우면 문제들에 압도당한 팀들이 좌절하게 되기 때문입니다. 그래서 먼저 난이도 커브를 정하고, 이에 맞춰서 어떤 문제를 내야 할지, 문제 풀에서 어떤 문제는 사용하고 어떤 문제는 사용하지 말아야 할지 등을 결정할 수 있습니다. 작년 서강대 대회 출제 후기에서 언급했던 것이기도 합니다.

UCPC 2020 출제 현황 시트 – 블로그 글의 이해를 돕기 위해 연출되었습니다

이와 관련해 작년 UCPC와 올해 UCPC의 가장 큰 차이는 문제의 난이도를 가늠할 수 있는 수단인 solved.ac의 존재였습니다. 서울 리저널의 난이도 분포를 참고해서 문제 풀에 있는 문제들의 난이도들을 각자 가늠해 보고, 예선과 본선 중 어느 쪽에 사용할지 플로우를 잘 돌렸습니다. 또한 시트에 추가적으로 알고리즘 분류를 적어 넣어, 한 분야에 너무 치우친 컨테스트가 되지 않도록 문제 배치를 잘 해주었습니다. 다만 검수 과정에서 난이도와 분류 정보를 보는 것은검수에 악영향을 끼칠 수 있어서 해당 시트는 출제진만이 편집했고, 검수 현황 시트를 따로 만들어 두었습니다.

문제 난이도를 가늠하는 현장

Call for tasks로 공모받은 문제들도 이렇게 출제진끼리 난이도를 가늠해 보고 대회에 필요하리라 생각된 문제들을 채용했습니다. 공모받은 문제들과 내부에서 출제한 문제들이 많아서 아쉽게도 사용할 수 없게 된 문제가 많았습니다.

디스크립션과 데이터 작성

Polygon

데이터의 무결성을 최대한 보장하기 위해 디스크립션 작성을 제외한 모든 작업은 Polygon에서 testlib을 사용해 진행했습니다. Polygon 서버가 다소 불안정해서 조마조마한 상황이 몇 번 있었고, 패키지 전체를 자주 백업했습니다.

solved.ac Overleaf

문제지는 solved.ac 서버에 올라가 있는 비밀의 시크릿 프린세스 self-hosted Overleaf에서 작업했습니다.

디스크립션데이터 작성에 있어서는 대회 참가자가 문제 디스크립션을 이해하거나 기타 문제에서 중요하지 않다고 생각되는 이슈들을 해결하는 데 시간을 소비하지 않도록 하게 하기 위해 다음 원칙에 따라 UCPC 출제 컨벤션을 만들고 이를 최대한 지키려 노력했습니다.

  • 일부러 디스크립션을 이해하기 어렵게 하는 컨셉의 문제가 아닌 경우, 디스크립션은 한 번만 읽어도 이해할 수 있을 정도로 이해하기 쉬워야 하며, 디스크립션이 문제 해결을 방해해선 안 됩니다.
    • 같은 맥락에서 어려운 수학 기호 및 개념은 가능할 경우 최대한 사용하지 않습니다. 갓 고등학교를 졸업한 사람이 봐도 이해할 수 있는 수준이어야 합니다. 이를테면, ‘$A$의 모든 원소들의 제곱의 합’이라고 적을 수 있는 것을 굳이 ‘$\sum_{a\in A} a^2$’라고 적지 않습니다.
    • 글과 문자가 쉽게 구분되면서 문자가 글을 읽는 데 방해가 되는 일이 없도록 해야 합니다. 따라서 모든 문자는 기울임꼴로 작성하고, 수식에는 띄어쓰기를 잘 하며, 기호는 정확한 것을 사용해야 합니다.
  • 입출력 변수의 범위는 입출력 섹션에 한꺼번에 명시하는 것이 가장 좋습니다.
  • 데이터는 무결해야 합니다. 명시한 형식과 다른 데이터가 있어서는 안 되며, 입력 데이터에 대한 정답이 아닌 데이터가 출력 데이터로 되어 있어서는 안 됩니다.
  • 생소한 입출력 방법 등으로 참가자가 고생하는 일이 없어야 합니다. 특히 EOF로 출력의 끝을 명시하는 입력 데이터들이 그렇습니다.

또한 디스크립션을 굳이 Polygon에서 작업하지 않은 이유가 있다면, BOJ의 문제 출제 플랫폼인 BOJ Stack의 존재 때문입니다. BOJ에서 대회를 개최하기 때문에 대회 전에 문제들을 전부 BOJ로 옮겨야 하는데요, 대회 직전에는 디스크립션 수정이 잦습니다. 근데 Polygon에서 디스크립션을 관리하게 되면 어차피 만들어야 하는 문제지에서도 디스크립션을 수정해야 하고, BOJ Stack에서도 디스크립션을 수정해야 합니다. 또한 BOJ Stack에서는 LaTeX을 최대한 쓰지 말아달라고 하기 때문에 TeX로 작성한 문제들을 전부 HTML로, 이를테면 $1 \leq N, M \leq 200\ 000$ $1 \leq N, M \leq 200\ 000$과 같은 식은 1 ≤ N, M ≤ 200 1 &le; <em>N</em>, <em>M</em> &le; 200 000으로 다시 포맷해야 하는데, 이는 생각보다 상당히 고통스럽습니다. 이런 공수를 줄이고자 Overleaf에서만 디스크립션을 관리했습니다.

물론 HTML로 포맷하는 공수조차 줄이고자 그냥 모든 문제에 TeX을 사용했습니다. 이렇게 할 경우 Overleaf에서 Stack으로 문제 본문을 복사+붙여넣기 하면 세팅이 끝납니다. 참 쉽죠.

문제 삽화가 그려지는 과정

문제와 해설에 사용될 그림의 경우, 그림을 요청하는 채널을 따로 만들어서 출제진의 요청을 받고 제가 그림을 열심히 제작했습니다.

마지막으로 정해는 ICPC 경향에 맞춰 C++과 Kotlin(또는 Java)으로 모두 작성했고, 해당 언어들로 문제를 해결 가능함을 보장했습니다.

검수

문제 초안이 완성되면, 문제를 검증하게 됩니다. 주로 다음과 같은 항목들을 검증해야 합니다.

  • 데이터와 디스크립션의 포매팅이 올바르고well-formed 무결한지 (UCPC 컨벤션을 지키는지)
  • 출제자의 풀이가 완벽한지
  • 출제자가 의도하지 않은 풀이로 풀리지는 않는지, 출제자가 의도하지 않은 풀이로 풀렸다면 그 풀이도 정답으로 허용할 것인지

검수진은 바로 위와 같은 항목들을 꼼꼼히 확인하는 역할을 합니다. 출제진들 사이의 교차 검수crosschecking 이외에도 외부 검수진을 모셔 와 검수를 진행했습니다.

검수의 흔적

참가팀들이 틀린 방법으로 접근할 것 같은 풀이(‘사풀이’라고도 합니다)를 미리 예상하고, 이를 적절히 막습니다. 방법은 여러 가지입니다. 예를 들어,

  • 어떤 자연수 $N$이 소수인지 판별해야 하는 문제에서, $N=1$이 입력으로 들어오는 경우를 제대로 처리하지 못하는 코드들을 틀리게 하기 위해 $N=1$인 데이터를 준비할 수 있습니다. (예외 처리 데이터)
  • 혹시라도 고려하지 못한 경우가 있을 수 있으니 랜덤에 의존해 제작한 일반적인 데이터를 추가로 더 준비할 수 있습니다.
  • 아예 틀린 방법으로 문제를 해결하는 코드가 준비한 데이터를 운좋게 전부 통과하는 경우가 있다면, 틀린 방법으로 풀면 틀리거나 시간 초과를 받는 데이터를 준비할 수 있습니다. (반례 데이터)
  • 출제자가 의도한 풀이보다 훨씬 쉬운 풀이로 풀리는 경우 시간 제한을 조정하거나 문제에 등장하는 상수들의 제한을 조정할 수도 있습니다. 의도한 풀이로 접근하면 풀리지만 의도하지 않은 풀이로 접근하면 시간 초과를 받도록 적절히 제한을 조정합니다.

등과 같은 과정을 거쳐 문제를 완벽하게 만들었습니다.

다들 모여

이 참가자는 WA를 받고 생존한 후에 다른 참가자를 소환합니다.

재작년과 작년의 참가 조건이 마음에 들었고, 그대로 유지했습니다. 석박통합과정 학생의 참가 조건만 명확하게 정했습니다.

  • 3명이 1개 팀으로 참가해야 함
  • 학부생이라면 재학과 휴학을 불문하고 참가 가능
  • 대학원생이라면 석사과정 또는 석박통합 2년차까지 참가 가능
  • 다른 학교 구성원끼리 팀을 이루어 참가 가능

수상 경력에 따라 제한 조건을 둘까도 생각해 봤는데, UCPC는 역시 한국 최강자전의 컨셉인 것도 있는 것 같아 딱히 두지 않았습니다. 총 299팀이 예선대회에 참가를 신청해 주셨습니다.

매년 하던 것처럼 Google Forms를 이용해 참가 신청을 받고 GMail을 통해 공지사항을 전송했는데, 대회 규모가 커지면서 이 방법을 계속 사용하기엔 다소 무리가 있는 것 같다는 생각을 했습니다.

  • 참가자 중 메일 주소에 오타를 낸 참가자들이 몇몇 계셨습니다. 실제 예시로, nvaer.comnaver.com, gamil.comgmail.com, kaist.co.krkaist.ac.kr 등으로 도메인에 오타를 낸 메일 주소들을 확인할 수 있었고, 제 수작업으로 고쳤습니다. 도메인 오타는 어찌저찌 고칠 수 있는데 @ 앞 부분에는 과연 오타가 없었을까요? 이메일 인증에 기반한 서비스를 사용하거나, 이를 직접 구현해야겠다고 느꼈습니다.
  • 예선 계정 정보 메일 299개를 하나하나 작성해 보냈습니다. 또한 GMail에 일일 전송 메일 수 제한이 있다는 사실을 이 대회 운영하면서 처음 알았습니다. 이 때 대량 자동 메일 전송 시스템을 구축했어야 했는데, 그러지 못했던 것이 후술할 예선대회 대참사로 이어집니다.

예산 확보

출제자 분들과 검수자 분들께 노력에 대한 합당한 대우를 드리고, 참가자 분들께 상을 드릴 수 있도록 예산을 정했습니다. 온라인 대회였기 때문에 장소나 비품, 식사 등에 대한 고려는 할 필요가 없다고 생각했으나.. 오산이었습니다. 온라인 방송을 할 장소와 장비가 필요했고, 상품이 있다면 상품을 배송할 비용도 필요했습니다. 대관료와 장비, 배송비 등을 종합해 보니 온사이트로 치뤄진 예년 예산만큼은 못해도 상당히 무거운 예산이 나왔습니다.

감사하게도 대회를 열겠다고 하자마자 케니소프트, 알고스팟, 스타트링크, 그리고 고려대학교 SW중심대학사업단에서 후원 의사를 보내 주셨습니다. 이후에도 여러 개발 기업에 무작정 메일을 보냈고(…) 마인즈랩네이버 D2에서 출제와 검수 비용을 지원해 주셨습니다. 대단히 감사합니다! (solved.ac의 경우 사실상 제 개인 후원이기 때문에 논외로 합니다)

온라인 대회여서 기존의 후원 세션을 기존처럼 진행할 수 없어 어떻게 어필할 수 있을까 고민했습니다만, 오히려 온라인 대회이고 공개된 온라인 방송으로 대회를 진행하기 때문에 굳이 참가자가 아니더라도 대회 방송을 볼 수 있고, 따라서 알고리즘 문제해결에 대한 충분한 관심이 있는 시청자들에게 기업을 홍보할 수 있다는 점을 어필하기로 했습니다.

사실 출제와 검수는 이전에 서강대 대회를 총괄해 본 적이 있었기에 어느 정도 익숙한 부분이었습니다. 하지만 서강대 대회는 학과 사업이어서 후원사를 구할 필요가 없었던 반면 UCPC의 경우에는 우여곡절이 많았고, 실수도 했습니다.

가장 큰 실수는 후원 조건을 명확하게 정하지 못했고, 또 이를 후원사에 제대로 공유하지 못했던 것이었습니다. UCPC에서는 홍보 기업 발표 세션을 진행하는데, 온라인 방송으로 홍보 세션을 진행하면 참가자들이 도중에 방송을 이탈해 효과가 반감될 것을 우려하여 후원 조건 금액을 높게 정했습니다. 그러나 죄송하게도 다른 바쁜 일들에 정신이 팔린 제 불찰로 인해 이를 공유받지 못한 후원사가 계셨습니다. 다음 대회부터는 이번 일을 반면교사로 삼고, 가능하다면 파이콘의 예처럼 후원 조건을 합당하고 명확하게 정하고 홈페이지에 공지하며 후원사와 긴밀히 소통할 수 있도록 해야겠습니다.

후원 기업들에 다시 한 번 무한한 감사를 드립니다.

예선 방송 리허설(7월 23일): 폭풍전야

대회 전에 리허설을 통해 방송 진행 계획을 구체화했습니다. 운영진들과 함께 안암의 명물 고고 인디안 쿠진에서 카레를 먹고, 대회 중 스튜디오로 사용될 고려대학교 정보대학 강의실에 숭고한 연합대회가 진행되는 도중 급습해 모여 강의실의 배치와 장비 등을 확인하고, 인터넷 속도는 방송을 송출하기에 충분한지, 강의실 구조 상 풀이 and/or 후원 세션은 어떻게 하면 좋을지 논의했습니다. 관련해서는 가장 고민을 많이 하신 재헌님의 UCPC 2020 방송 후기를 참고하시면 좋습니다.

OBS가 브라우저 화면을 겹쳐 띄워 둘 수 있다는 것을 이용해 기획한 대로 제가 대회 직전(당일 새벽 3시까지!)에 React로 간단하게 방송 씬을 만들었습니다. 코드는 여기 올라와 있습니다.

예선(7월 25일): 이 곳이 어둠의 기운으로 가득차 곧 무슨 일이 일어날 듯 합니다

방송 씬을 만드느라 날을 새고 10시에 고려대에 도착해서 세팅을 시작했습니다. 운영진들은 과연 올솔이 몇 팀이나 나오고 언제 제일 먼저 나올까 내기를 합니다.

막상 대회 시간이 다가오니까 대회 진행 플랫폼인 BOJ가 500 Internal Server Error와 502 Bad Gateway 에러를 뱉기 시작합니다. 어…? 이대로는 대회를 정상적으로 진행할 수 없겠다 싶어 대회 시작을 14:10으로 10분 연기했습니다.

그런데 좀 잠잠해지나 싶더니 14:07쯤 되니까 같은 현상이 다시 일어나는 거였습니다. 10분을 더 연기할까 고민하고 있던 찰나, BOJ Stack도 먹통이 되었습니다. 대회 정보는 BOJ Stack이라는 관리 페이지에서 수정해야 합니다. BOJ Stack이 접속이 막히면 운영진도 대회 시간을 수정할 수 없습니다.

Media Tweets by Haachama Sukidesu (@Haachama_Suki) | Twitter

’14:20에는 괜찮아지겠지? 그럼 종료 시간을 10분쯤 연장해야겠다’고 생각했습니다. 서버는 비웃기라도 하듯 계속 접속을 거부했습니다.그런 와중에 UCPC 관련 메일 아래에 적혀 있던 제 전화번호로 참가팀들의 전화가 걸려오기 시작했습니다. 운영진도 대회 서버에 접속할 수 없는 상황임을 알려 드렸습니다.

이제는 대회를 어떻게 할지 빠르고 공정하게 결정해야 했습니다. 특히 여러 지역에서 모여서 대회를 치기 위해 스터디룸을 빌리는 팀이 많다고 알고 있어, 이 분들의 피해가 최소화되도록 하려면 어떻게 해야 하나 고민했습니다. 그 와중에 대회 사이트에서 문제 제목을 읽은 팀이 있다는 제보를 받았습니다. 문제 제목을 읽은 팀이 있었다는 것은 문제를 읽은 팀이 있었을 수 있다는 뜻도 되므로 신중하게 결정해야 했습니다.

정말 다행히도 이번 UCPC는 온라인 대회였기에 공간의 제약을 받지 않았고, 본선에 몇 팀이 출전하더라도 수용할 수 있었습니다. 그래서 아예 기업 대회 Qualification 라운드의 형식으로 본선 커트라인을 명시하고 시간을 대폭 연장하기로 결정합니다. 그리고 단체 메일을 보냈습니다.

그런데, 감사하게도, 구글이 제가 아이패드(구글 계정 입장에서는 ‘새로운 기기’)로 대량의 메일을 보내려는 시도를 감지하고 전대프연 계정을 차단시켰습니다. 제가 회장이 되면서 바꾼 비밀번호로는 로그인이 안 됐고 이전 비밀번호로 인증을 받아야 했는데, 무려 제 전전전임 회장께서 설정하신 비밀번호였습니다. 당장 연락을 드렸지만 회신을 무작정 기다릴 수도 없는 상황이었어서, 제 개인 메일으로 단체 공지와 문제지를 전송했습니다. 이 때가 오후 3시였습니다.

서버 상황은 오후 4시를 즈음하여 개선되었고 코드를 제출할 수 있는 상황이 되었습니다. 예선대회에서도 해설과 스코어보드 공개를 할 예정이었고, 이를 위해 출제진들이 모여 있었으나, 안타깝게도 이후 방송은 진행하지 못했습니다. 여러모로 아쉬웠습니다. 예선 당일 서버 사고가 일어났던 이유는 여기에서 읽을 수 있습니다.

결과적으로 본선 진출 팀은 총 170팀이 되었고 의도치 않게 역대 최대 규모의 본선이 되었습니다. 컷을 5문제로 잡았으면 좋았겠다는 의견도 많았으나 안타깝게도 결정 당시에는 출전 팀들의 실력을 가늠하기 힘들었습니다.

본선도 규모가 예선 못지않게 커져버렸고, 본선대회에서도 같은 일이 되풀이되면 안 되었기에 만반의 준비를 했습니다. 우선 스타트링크와 함께 이런 일이 일어난 원인을 분석하고 당시 일어났던 상황이 재발되지 않게 하기 위한 기술적 조치를 취했습니다. 본선도 BOJ에서 치루는 것으로 결정했으나, 혹시 모를 사태에 대비하기 위해 UCPC 운영진도 DOMjudge 서버를 직접 구축해 대비했습니다.

본선(8월 1일):

이미지
대회 본부

정말 다행이게도 본선대회는 아무 문제 없이 순조롭게 잘 진행되었습니다.

서버 대비와 별개로 본선대회를 위해 준비한 것들이 몇 개 더 있었습니다. 그 중 가장 눈여겨볼만했던 건, 대회 중에도 공지했던 바 있지만, 이번 UCPC는 스코어보드 공개의 재미를 위해 프리즈 기준을 다소 특이하게 잡았던 것이었습니다.

프리즈 시간은 대회 종료 60분 전이었으나, 프리즈 시간과 관계없이 어떤 팀이 9문제를 해결한 순간 이후 그 팀의 모든 제출이 비공개되는 식이었습니다. 이를 위해 BOJ에서 스코어보드를 제공하지 않고 scoreboard.ucpc.me라는 별도의 사이트에 스코어보드를 띄웠습니다. 박수찬tncks0121님께서 운영자 스코어보드의 모든 제출을 $n$초마다 가져와서 가릴 건 가리고 공개 스코어보드에 보여주는 파이썬 스크립트를 짜 주셔서 가능했던 일이었습니다.

풀이는 원래 라이브로 방송하려 했으나, 풀이 슬라이드가 대회 중에 완성되기도 했고, 진행을 비교적 여유롭게 하기 위해 녹화방송으로 바꿔 진행했습니다. 풀이 슬라이드가 방송에서 사용한 것과 공개된 것이 다른데, 방송에서의 풀이 슬라이드는 출제진이 생각한 난이도 순서대로 문제가 정렬되어 있고, 공개된 풀이 슬라이드는 A, B, C.. 순입니다. 특별한 이유가 있다면 공개 슬라이드에서는 풀이가 필요한 문제를 더 빨리 찾게 하기 위함이었습니다.

본선 해설 및 스코어보드 공개 영상

본선대회를 성공적으로 종료하고 저를 제외한 운영진은 고기를 맛있게 먹으러 갔다고 합니다. 저는 안타깝게도 당일 너무 피로해서 그냥 집에 와서 곯아떨어졌습니다. 비극적인 엔딩이네요.

어땠나

힘들지만 의미있는 경험이었다고 말하면 식상할까요?

힘들었던 것들

전대프연 회장을 하는 것은 힘든 일이라고 익히 들어서 알고 있었지만 구체적으로 어떤 게 힘든 일인지는 잘 몰랐습니다. 이제 대략 알게 되었습니다.

  • 후원사를 구하기가 상당히 힘들었습니다. 온라인이어서 더 그랬습니다. 올해 ICPC도 후원 사정이 좋지 않다는 걸 생각하면 어디나 비슷한 것 같습니다. 그래서 개인적으로 옆 나라 리저널의 후원 사정이 꽤 부러웠습니다.
  • 상금 처리라던가 참가 상품을 택배로 보내야 했던 것도 힘든 점 중 하나였습니다.
  • 6월 말에 넥슨코리아 엔진스튜디오에 산업기능요원으로 입사하게 되었습니다. 6월까진 무직 백수여서 몰랐는데, 대회 코디네이팅과 왕복 3시간의 출퇴근 생활을 병행하는 것은 체력적으로 상당히 힘든 일이었습니다. 회식 도중에 나와서 대회 개최를 준비했던 적도 있습니다.
  • 이외에는, 참가자 등록이 번거로웠습니다. 특히 재학증명서와 휴학증명서 등을 일일이 확인하는 작업이 너무 번거롭고 힘들었습니다.
후원사 구하기와 재학증명서 처리는 사천왕 중에서도 최약체…

그렇지만 저는 온사이트 대회를 준비했던 건 아니었기 때문에 다른 해보다는 비교적 쉽게 준비한 것이 아닌가 싶습니다.

놓쳤던 것들

대회 중에 발생한 이런저런 예외적인 상황들에 대해 제대로 and/or 빠르게 대처하지 못했던 게 아쉬웠습니다. 앞서 언급했던 것들만 다시 언급하자면, 가령…

  • GMail은 하루 발신 제한량이 있었고, 제가 shiftpsh.com과 ucpc.me 도메인으로 발신하기 위해 개인적으로 월정액을 내고 사용했던 메일서버에도 마찬가지로 하루 발신 제한량이 있었습니다.
  • 사소하게는 call for tasks 메일 계정과 Slack을 연동시켜 주는 Zapier 스크립트가 간헐적으로 동작하지 않는 문제가 있어서 공모받은 문제를 확인하지 못할 뻔 하기도 했습니다.
  • 이메일 주소를 이메일 확인 등의 방법으로 검증하지 않아 메일 전송 과정 중에서도 누락되는 메일들이 발생했습니다. Google Forms를 사용하는 대신 직접 대회 등록 사이트를 만들었다면 좋았을 것입니다. 위의 이유와 맞물려 메일을 자동으로 발송해 주는 시스템이 있었다면 더 좋았을 것입니다.
  • 대회 서버가 다운되었을 때 의논할 시간이 부족한 상황에서 대회 진행 형식을 결정해야 했습니다. 이런 상황이 발생했을 때는 미리 어떻게 하면 좋겠다는 계획을 세웠더라면 참가자들의 피해와 스트레스를 줄일 수 있었을 것입니다.
  • 방송 계획과 대본을 미리 짜놓지 못해 다소 매끄럽지 못하게 진행된 것 같아 아쉬웠습니다.
  • (이건 예외적인 상황은 아니었고, 제가 정신없어서였지만) 제 불찰로 후원사와의 소통이 제대로 진행되지 못한 부분이 있었고, 안타깝게도 이로 인해 다소 불미스러운 일이 발생했습니다.

UCPC는 이제 예선 기준으로 참가 인원 1,000명을 바라보고 있을 정도로 커진 대회입니다. 코딩 테스트 열풍으로 인해 알고리즘 문제해결 및 경쟁 프로그래밍 입문자들이 갈수록 많아지고 있고, 이런 기조가 사그라들지 않는 한 대회 규모는 계속 커질 것으로 생각됩니다. 이런 규모의 대회라면, 이런 규모의 대회에서 일어날 수 있는 갖가지 상황들을 미리 파악하고 숙고하여 대비하는 것이 옳겠다고 뒤늦게 느낍니다.

그럼에도 좋았던 것들

우여곡절도 많았지만 전대프연으로써는 처음 시도했던 온라인 대회를 성공적으로 마무리할 수 있어서 뿌듯합니다. 코로나 시국도 프로그래밍 대회는 이기지 못했네요. 😎

대단하고 멋진 분들과 함께 최고의 커뮤니티 대회를 만들 수 있어서 즐겁고 뿌듯했습니다. 온라인 대회여서 안타깝게도 참가자 분들과는 만나지 못했지만요.

그래서 회장 1년 더 하나요?

이 팀은 사실 시프트를 제외한 레드시프트 팀인데, 제가 대회 같이 안 치고 혼자 운영하러 가서 삐진 나머지 이름을 이렇게 지었나 봅니다

사실 UCPC 2020 직후에도 SUAPC에서 운영과 출제를 했고, 신촌 캠프 대회와 SNUPC에서도 검수 및 조판으로 참여했습니다. UCPC에서 얻은 경험과 리소스가 상당히 많은 도움이 되었습니다.

운영진 16콤보

하지만 내년에도 회장을 할지는 잘 모르겠습니다. 일단 이제 직장에 다니고 있기도 하고, 무엇보다 결정적으로 휴학생 팀으로 나간 ICPC 2020 인터넷 예선에서 너무 처참한 성적을 받았기 때문입니다.

여러 대회를 운영하고 출제하고 검수하느라 바빴고, 게다가 solved.ac 개발로도 바빴던 나머지 제가 문제해결 연습을 할 시간이 부족해져서라고 생각했고, 지금은 다시 팀 연습도 하고 코드포스도 자주 나가고 BOJ 문제도 열심히 풀고 있습니다. 내년 대회에는 참가자로서 참여하고 싶습니다. 복학할 때 적의환향赤衣還鄕해서 레드시프트 이름값 해야죠.

이런 현실이… 이런 현실이 있단 말이냐?

결론은 내년 전대프연을 이끌어 주실 분을 모십니다. 뭐 없으면 어쩔 수 없고…

마치면서

전대프연과 UCPC에 특별한 관심을 갖고 지원해 주신 고려대학교 SW중심사업단, 마인즈랩네이버 D2, 그리고 알고스팟구종만님과 케니소프트박현민525hm님께 깊은 감사를 드립니다. 특히 예선 서버 문제로 새벽 코딩을 불사하며 끝까지 이슈 해결을 위해 수고해 주신, 전대프연의 초대 회장이자 이제는 스타트링크최백준baekjoon님께 각별한 감사를 드립니다.

검수는 물론 대회 운영 전반에 있어서 기술적으로 많은 도움을 주신 박수찬tncks0121님, 훌륭한 문제를 출제해 주신 김동현kdh9949님, 김창동sait2000님, 나정휘jhnah917님, 노영훈Diuven님, 모현ahgus89님, 문창현ckdgus2482님, 반딧불79brue님, 배근우functionx님, 심유근cozyyg님, 이동관windflower님, 이상헌evenharder님, 이종영moonrabbit2님, 정기웅QuqqU님, 조창민Ronaldo님, 그리고 열정적으로 검수해 주신 류호석rhs0266님과 홍은기pichulia님께 진심으로 감사의 말씀을 드립니다.

이외에도 대회 막바지에 운영에 큰 도움을 주신 공인호inh212님, 김영현kipa00님과, 실험적인 형태의 대회임에도 대회 진행자를 흔쾌히 맡아 주신 정재헌Gravekper님께도 대단히 감사드립니다.

마지막으로, UCPC 2020에 참가해 주신, 알고리즘 문제해결을 사랑해 주시는 참가자 여러분께 감사드립니다.


대회 리소스

다른 프로그래밍 대회를 개최하시는 데 도움이 될 수 있도록 UCPC 2020에서 사용된 자료들을 인터넷에 공개했습니다.

오픈소스

  • ucpcc/ucpc2020-site UCPC 2020 대회 사이트
    Jekyll로 제작한 정적 사이트입니다. 대회 공지사항 등을 적기 위해 만들었습니다.
  • ucpcc/problemsetting-guidelines UCPC 디스크립션 작성 및 포매팅 컨벤션
    UCPC 문제 제작을 위해 수립한 컨벤션입니다. 일관적인 데이터와 디스크립션 작성에 도움을 줄 수 있습니다.
  • ucpcc/ucpc2020-description-layout UCPC 2020 문제지 레이아웃
    마개조된 olymp.sty입니다. 2019 서강대학교 프로그래밍 대회 문제지 레이아웃을 바탕으로 제작했습니다.
  • ucpcc/ucpc2020-solutions-theme UCPC 2020 솔루션 Beamer 테마
    Beamer와 함께 사용할 수 있는 테마입니다.
  • ucpcc/ucpc2020-broadcast-scene UCPC 2020 방송 씬
    OBS가 씬에 웹 브라우저를 사용할 수 있다는 사실에 착안하여 React로 제작한 16:9 방송 씬입니다.

공유 문서

  • UCPC 2020 문제 출제 현황 시트
    대회 운영에 활용했던 출제 현황 시트 레이아웃입니다. 운영 당시 그대로의 시트는 아니고, 이해를 돕기 위해 체크박스 상황은 연출했으며 미사용 문제들은 다른 곳에서 사용될 수 있으므로 관련 정보를 지웠습니다. 사용하고자 하실 경우 파일 > 사본 만들기를 누르면 수정 가능한 사본을 만들어 사용해 주세요.

다른 글

2019 서강대학교 프로그래밍 대회를 개최했습니다

제목이 곧 내용, 올해 서강대학교 교내대회를 개최했습니다. 제가 출제와 운영을 총괄했습니다! 진짜 구데기컵 2018(…)을 제외하면 제 첫 출제였고, UCPC 2018에 풍선 스탭으로 참여한 걸 제외하면 첫 운영이었습니다. 대회를 준비하고 진행하면서 느낀 점들을 적어보려 합니다.

어떤 걸 먼저 해야 하지

서강대학교의 경우 2005년부터 매년, ICPC Korea Regional에서 교내 랭킹 1~2위의 팀이 대회 운영과 출제를 해왔습니다. 대회는 보통 11월 말이고 ICPC 본선은 11월 초에 진행되기 때문에 보통 2~3주 정도의 준비 기간이 주어집니다. 아니 대회 운영하려면 뭐가 필요한지도 모르는데 2주만에 대회 준비를 어떻게 해요??

근데 사람 일이 다 그렇듯이 놀랍게도 대회 날짜가 다가오면 준비를 하게 되어 있더라고요. 학교에서 ICPC 본선에 3팀이 출전했는데, ‘그래도 우리 팀이 그 중에서 적어도 2위는 하겠지?’ 라는 행복회로를 돌리면서 김칫국 109 + 7사발 마시고 미리 대회 개최를 준비했습니다.

대회를 열기 위해 필요한 것들은 대략 다음과 같습니다.

  • 문제. 문제 푸려고 여는 대회인데 문제가 없으면 안 되죠.
    • 출제진. 애초에 출제진이 없으면 문제가 나올 수가 없죠.
    • 검수진. 물론 출제진이 검수해도 괜찮습니다.
  • 시간과 공간. 언제 어디서 개최할지 결정해야 합니다.
  • 참가자. 이건 제가 어쩔 수가 없고..
  • 포스터, 풍선, 간식 같은 거

이 중에 제가 그 시점에 할 수 있었던 건 문제 만들기였습니다. Redshift가 본선 교내 1~2등 안에 들면 세 명 모두 출제를 해야 했기에, 일단은 팀 내에서 문제를 뭐 낼지 어렴풋이 생각해 보기로 했습니다. 대충 이런 문제 아이디어가 나왔습니다.

  • 7-세그먼트 디스플레이 (shiftpsh 아이디어)
    • 개인적으로 좋은 문제라고 생각했어서 제네레이터까지 만들어 뒀습니다.
  • 올솔브 방지용 graph isomorphism 문제 (shiftpsh 아이디어)
    • Tree isomorphism 문제로 약화되어 출제되었습니다. 이거 오렌지 이상에선 웰노운이라더라고요. 왜 내가 웰노운이 아니라고 생각했던 건 다 웰노운이지??
  • 최단 경로 문제인데, 길이의 곱이 최단이 되어야 하기 때문에 간선 가중치에 전부 로그를 씌워 구하는 문제 (lvalue 아이디어)
    • 방콕 리저널 예비소집일에 나온 아이디어였는데, 다음날 본대회 F번?으로 실제로 나와버렸기 때문에 표절 의혹을 받을까봐 실제 대회에는 못 냈습니다.
    • 이 아이디어 덕분에 대회 당시 F번을 두번째로 빨리 푼 팀이 우리 팀이었는데, 역설적이게도 대회를 말아먹는 계기가 되었습니다. 이건 다른 포스트에 후술.

다행히도 Seoul Regional에서 교내 1등을 하는 데 성공했기 때문에 대회 운영에 참여할 수 있게 되었고, 이제 아까 언급한 ‘대회 운영에 필요한 사항’ 4개 전부를 고려해야 하는 상황이 되었습니다.

문제

각자 내고 싶은 문제들은 있겠지만, 실제로 모두가 각자 내고 싶은 문제만 낸다면 프로그래밍 대회가 아니라 빡구현 코딩테스트가 될 수도 있고, 고인물 자료구조 파티가 될 수도 있고, 계산수학 경시대회가 될 수도 있겠다고 생각했습니다. 그래서 아래와 같은 원칙을 두고 대회 문제들의 전체적인 틀을 정했습니다.

  • 출제되는 문제들의 주제는 균형적이어야 합니다.
    • 12문제 중 DP가 5문제고 그러면 좀.. (ICPC Seoul 예선 출제자님 듣고 계신가요)
  • 모든 문제를 푸는 사람은 없어야 하고 하나도 못 푸는 사람이 있어서는 안 됩니다.
    • ICPC 출제 기조라고 들었습니다.

개인적으로는 tree isomorphism 문제를 너무 내고 싶었기 때문에 디비전 당 8문제, 총 16문제를 내기로 정했습니다. 지금 생각해보면 그러지 말거나 아니면 디비전끼리 겹치는 문제를 내게 하거나 했어야 했는데 아무튼 그렇게 정했습니다.

구데기컵에서 문제 정리를 위한 스프레드시트를 팠던 기억이 있어서 저도 그렇게 했습니다.

쉬운그래프문제하나만더있으면좋겠다

20문제를 낸 후, 각각 solved.ac 기준 예상 난이도와 사용 알고리즘/자료구조를 적고 8개 문제씩 Champion/Master에 각각 배정해나가면서, 대회가 너무 어려워질 것 같아서 많이 등장한 주제의 문제 중 4문제를 뺐습니다. 그리고 동그라미를 하나씩 채워나가는 방향으로 문제를 준비했습니다.

예상 난이도를 정하는 것에 장점과 단점이 있었는데요,

  • 장점은 문제를 배정하기가 쉬웠다는 점이었고,
  • 단점은 문제 난이도를 잘못 예상해서 Master 디비전 스코어보드가 망했다는 점이었습니다.
    • 출제자 생각과 참가자 생각은 많이 다르다는 걸 깨달았고, 조금 더 참가자 입장에서 문제 난이도를 생각해 보려고 노력해야겠다고 느꼈습니다.

문제들을 배정하고 나서는 스테이트먼트와 정해 – 데이터와 테스트 – 제한 순서대로 문제를 만들었습니다.

스테이트먼트

참가자 입장에서 스테이트먼트는 명료할수록 좋습니다. 러시아의 모 사이트에서 열리는 대회에는 스테이트먼트가 애매하거나 이상한 경우가 종종 있었는데, 이로 인해 스트레스를 받은 경험이 있었기 때문에, 참가자가 문제를 푸는 데 방해가 되지 않도록 스테이트먼트를 구성하려고 노력했습니다. 출제 의도가 스테이트먼트를 길게 해서 일부러 문제풀이를 지연시키고자 하는 게 아니라면 쓸데없는 이야기는 줄이고 문법상의 오류나 비문은 최대한 없애고, 문장은 짧게 구성하도록 하고. 사실 글쓰기의 기본이죠.

스테이트먼트 원고는 각자 작성하고 Stack에 옮기면서 수정했습니다

아래는 카드 놓기의 스테이트먼트 원고와 최종본을 비교해 둔 것입니다.

첫번째 줄에는 N(1<=N<=1000000)이 주어진다.
두번째 줄에는 길이가 N인 수열 A가 주어진다.
Ai가 1이면 i번째로 카드를 내릴 때 1번 기술을 썼다는 뜻이다.
Ai가 2이면 i번째로 카드를 내릴 때 2번 기술을 썼다는 뜻이다.
Ai가 3이면 i번째로 카드를 내릴 때 3번 기술을 썼다는 뜻이다.
Ai는 1,2,3중 하나임이 보장된다.
An은 항상 1임이 보장된다.
첫 번째 줄에는 N (1 ≤ N ≤ 106)이 주어진다.
두 번째 줄에는 길이가 N인 수열 A가 주어진다. Aix이면, i번째로 카드를 내려놓을 때 x번 기술을 썼다는 뜻이다. Ai는 1, 2, 3 중 하나이며, An은 항상 1이다.

위와 아래 중 어떤 글이 더 이해하기 쉬운가요? (아래라고 해주세요)

  • 1000000은 한 눈에 봤을 때 정확히 얼마인지 가늠하기 어렵기 때문에, 1,000,000 또는 106으로 고쳐야 합니다.
  • <=은 정확한 표현이 아니기 때문에 ≤으로 고칩니다. 변수명은 일반적인 글과 구분하기 쉽게 하기 위해 기울임꼴으로 씁니다. 기울임꼴로 하는 편이 실제 수식에서 등장하는 문자들의 모양과 비슷하기도 하고요.
  • 쉼표로 나열된 항목들은 띄어쓰기로 구분해 줍니다(1,2,3 → 1, 2, 3).
  • 같은 의미의 글이 여러 번 반복되는 경우 간단하게 줄일 수 있는지 생각해 봅니다.

스테이트먼트를 그냥 읽어보면 모르겠지만, 계속 긴장 상태일 참가자 분들의 입장에서 편하게 읽힐 수 있도록 이렇게 나름대로 세심하게 편집하는 작업을 거쳤습니다.

대농부 김상혁
평행우주

또한 글만 적혀 있으면 문제를 이해하는 데 어려움이 있을 수도 있기 때문에 몇몇 문제에는 적절한 위치에 삽화들을 그려서 삽입했습니다. 다행히도 제가 그래픽 디자인을 할 줄 알았기 때문에 삽화는 제가 전부 그렸습니다.

정해

출제자의 정해가 틀리면 안 됩니다. 최근 열렸던 학교 대회 중에 출제자가 잘못된 풀이를 작성한 경우가 종종 있었고, 서강대학교만 해도 2017년에 출제자가 정해를 잘못 작성했던 적이 있었습니다. 특히 대회 준비 중에 열린 타 대학교 대회에서 이런 상황이 발생했기 때문에 각별히 신경써서 검수했습니다.

이건 어쩔 수 없지

학과에서는 외부 검수자를 초빙하지 말라고 했지만 2주 안에 16문제를 출제해야 하는데 출제 경험이 적은 6명이 오류를 안 내는 게 이상한 상황이었고, 결정적으로 스타트링크에서 검수자 초빙을 말 그대로 강력하게 권장했기 때문에 제 단독 판단으로 검수자를 초빙하기로 했습니다. 여러 대회에 검수자로 참가했던 분들이었기에 문제 보안에 대해서는 믿을 수 있었으며, 개인적으로는 잘못된 문제를 만들지 않는 것이 더 중요하다고 생각했기 때문입니다.

아니나다를까 색깔 하노이 탑 문제에서 출제자의 정해가 잘못되었음이 외부 검수자에 의해 발견되어 일찍 수정할 수 있었습니다. 검수자 분들께서는 후술할 데이터와 시간 제한에 대한 검증도 진행해 주셨습니다. 외부 검수자 분들께서 안 계셨다면 이번 대회도 문제 오류가 있는 대회가 될 뻔 했는데, 참 다행이라고 생각합니다. 이 글을 빌어 감사하다는 말씀을 다시 한 번 전해드리고 싶습니다.

데이터와 테스트

저는 Polygon을 이용해 데이터를 만들었습니다. Polygon은 Codeforces에서 프로그래밍 문제 제작을 도와주는 목적으로 만든 플랫폼입니다. 다음과 같은 것들이 가능합니다.

  • 데이터 생성 및 관리
  • 입출력 형식 검증
  • 출력 가능한 스테이트먼트 PDF 제작

특히 입력 형식의 무결성은 상당히 중요하기 때문에 Polygon을 사용합니다. 예를 들어 평행우주는 입력이 다음 조건을 만족함이 보장되어야 합니다.

  • 입력으로 들어오는 각각의 그래프는 연결되어 있어야 하며 트리여야 합니다. 노드가 s개라면 노드 번호는 0, 1, 2, …, s − 1로 주어져야만 합니다.
  • 입력으로 들어오는 트리의 개수는 106개 이하여야 합니다. 각 트리의 노드의 개수는 30개 이하여야 합니다. 또한 모든 트리의 노드 개수의 합도 106개 이하여야 합니다.
  • 당연하겠지만, 입력의 각 줄마다 맨 앞이나 맨 뒤에 공백 문자가 있어서는 안 되고, 공백이 두 개 들어가 있다거나, 맨 마지막 줄에 줄바꿈이 없다거나 하는 경우도 안됩니다. C/C++로는 어찌저찌 잘 풀릴지도 모르지만 Java와 Python에서 제대로 풀리지 않을 수 있기 때문입니다.

testlib.h는 위와 같은 무결성 체커를 간단하게 짤 수 있도록 해 줍니다. 또한 Polygon은 이렇게 짠 무결성 체커를 바탕으로 데이터를 제작할 때 모든 데이터에 대해 생성과 동시에 자동으로 무결성 체크를 해 주기 때문에 다른 더 중요한 것들에 집중할 수 있게 해 줍니다.

Polygon에 등록되어 있는 서강 프로그래밍 대회 문제들

testlib.h는 데이터 생성기를 제작할 때도 사용할 수 있습니다. 안타깝게도 다른 출제자 분들께서는 Polygon 사용 경험이 없었고, 출제 시간도 2주로 상당히 촉박해서 급한 대로 testlib.h를 쓰지 않는 제네레이터를 만들었습니다. 실제로 도중에 입력 조건에 맞지 않는 데이터가 업로드되었는데, 무결성 체커가 없어서 찾아내기 어려웠습니다. 다행히도 대회 직전에 찾아서 삭제했습니다.

하지만 Polygon 자체도 문제 제작자가 짠 코드에 의존하기 때문에, 대부분의 실수를 잡아낼 수 있다고 하더라도 잡아내기 어려운 실수들도 있습니다. 가령 평행우주의 경우는 날 새가면서 정신없이 문제를 만들다 보니 제가 10만과 106을 헷갈렸나 봅니다. 문제에는 별의 수와 별자리의 수가 106을 넘지 않는다고 되어 있습니다만 실제로는 각각이 105을 넘지 않는 약한 데이터만을 준비했던 사실을 시상식 때가 되어서야 깨달았습니다.

데이터 자체는 무결했으니 문제나 데이터의 오류는 아니었지만, 의도하지 않은 풀이가 통과할 수도 있었던 상황이었습니다. 대회 당시 맞은 사람이 없었어서 다행이었을까요? 곧 데이터를 추가할 예정입니다.

제한

시간 제한과 메모리 제한

의도한 풀이는 통과하고, 의도하지 않은 풀이는 통과하지 않도록 제한을 잘 설정해야 합니다. 그런데 이게 은근 어려운 게,

  • 언어마다 실행 시간에 차이가 있습니다.
    • Python에서 최적의 솔루션이 동작하는 시간이 C++에서 나이브한 솔루션이 동작하는 시간보다 느릴 수도 있습니다.
  • 같은 언어의 같은 풀이라도 입력 방식에 따라 실행 시간에 많은 차이가 생깁니다.
    • 느린 입력 방식 기준으로 시간 제한을 설정하면, 빠른 입력 방식을 사용했지만 느린 알고리즘을 사용한 코드가 시간 안에 통과하는 상황이 생길 수도 있습니다.

solved.ac는 어떤 방식으로 정렬을 해도 괜찮은 문제이지만, 원래는 인덱스 소트를 이용해 O(n)으로 정렬해야만 하는 문제로 기획되었습니다. 그래서 기존에는 제한이 n ≤ 107이었습니다. 하지만 C++에서 빠른 입력을 쓰고 std::sort를 사용한 코드가 500ms 좀 넘게 돈 반면, Java에서 인덱스 소트를 이용한 코드가 800ms가 나오길래, 그냥 포기하고 쉽게 바꿨습니다.

작년 대회와 다르게 이번 대회에서는 Python을 사용할 수 있도록 했지만 정작 모든 문제가 Python으로 풀릴 것이라고 보장하지는 않았습니다. 대회 규칙에는 ‘출제진이 모든 문제를 C++과 Java 혹은 Kotlin으로 풀었음이 보장됩니다’라고 적었고, 실제로 C++과 Java 혹은 Kotlin으로 모든 문제를 검증했으나 Python으로는 풀리지 않는 문제도 있었습니다. Python은 현저히 느리기 때문에 Python 기준으로 시간 제한을 잡으면 C++ 나이브 코드가 통과할 수도 있기 때문이었습니다. 이는 ICPC World Finals 규칙과도 같습니다.

메모리 제한은 전부 1024MB로 설정했습니다. 여러 테스트를 돌려 보면서 틀린 코드가 맞거나 맞은 코드가 틀리는 등의 상황이 발생하면 제한을 조절하거나 데이터를 보강하는 등의 작업을 진행하면서 문제를 완성시켜 나갔습니다.

완성된 시트

그렇게 시트를 전부 동그라미로 만든 후에는 한 숨 돌릴 수 있었습니다.

운영

문제를 다 만들어갈 때쯤에는 대회 운영에 대한 것도 생각해야 했습니다. 다행히도 학과에서 다년간 대회 운영을 도와주고 계셨기 때문에 이런 것들은 별 고민 없이 해결되었습니다.

대회 진행 전

  • 장소. 실습실은 학교가 관리하고 있기 때문에 대여도 학과에서 처리해 주셨습니다.
  • 풍선. 역시 학과에서 지원해 주셨습니다.
    • 헬륨 풍선이 생각보다 비쌌습니다. 하나에 1,500원이었는데, 대회 끝나고 못 나눠준 풍선은 다 터뜨려야 한다고 생각하니 좀 안타까웠습니다.
  • 간식. 이것도 학과에서 지원해 주셨습니다.

포스터는 학과에서 인쇄만 해 주기 때문에 제가 만들어야 했습니다. 팀노트에 있던 디닉 코드를 가져와서 3시간동안 간단히 만들었습니다.

애프터 이펙트로 간단하게 배경을 만들고
포토샵으로 글씨를 얹었습니다

학내 이곳저곳에 포스터를 붙이고, 참가자는 학교 커뮤니티에 게시글을 올려 모집했습니다. 총 91분께서 참가 신청을 해 주셨습니다. 안타깝게도 당일에 안/못 오신 분들이 많아서 실제 대회 당일에는 약 70분 가량 참여해주셨습니다.

이미지
문제지

문제지도 만들었습니다. 문제지는 LATEX로 타입세팅해 대회 전날에 인쇄했습니다. 학과사무실에서 인쇄해 제본과 운반을 전부 수작업으로 했는데, 대회 운영 중 가장 힘들었던 일이 아니었을까 싶습니다.

대회 당일

이미지
데스크

대회 당일 운영은 순조로웠습니다. 다만 실습실에 PyCharm과 IntelliJ를 설치하느라 시간이 오래 걸렸는데, 전날에 미리 설치해 뒀다면 좋았겠다 싶었습니다. 실습실이어서 학생들이 작성한 코드가 남아 있었고 이들을 전부 지우기 위해 현장에서 배치 파일(.bat)을 급조해 모든 컴퓨터에서 돌렸습니다.

이미지
이 많은 풍선들 중 단 하나만이 참가자의 손에 들어갔다는 슬픈 소식

풍선을 나눠주려면 자리표가 있어야 편한데 이 사실을 간과했습니다. 자리표도 현장에서 가나다순으로 급조했습니다. 이런저런 일들로 인해 대회 초반에 풍선이 늦게 나가는 일이 있었습니다.

Champion 디비전은 별 일이 없었지만 Master 디비전 스코어보드는 꽤 오랫동안 많은 사람들이 1솔브에서 머물러 있었습니다. B번 문제의 난이도를 잘못 생각했음을 직감했습니다. 다음날 진행된 Open Contest에서도 B번 문제가 많이 풀리지 않았습니다. 심지어 ainta님은 A~P를 전부 푼 후 마지막에 B를 푸셨을 정도.

많은 사람들이 겹받침이 등장할 때 도깨비불 현상이 일어난다는 걸 간과한 듯했고, 대회가 시작한 후 꽤 지나서 공지사항으로 추가 테스트 케이스를 제공했습니다. 이 테스트 케이스 덕분에 B를 맞게 된 분들이 몇 분 계셨지만 애초에 Master B번으로 낼 만한 수준의 문제가 아니었다는 걸 생각하지 못했던 건 아쉬움으로 남습니다. 초보자에게 문자열 처리는 생각보다 어려운 주제인가 봅니다.

대회 운영진은 참가자의 소스코드를 전부 읽어볼 수 있기 때문에, 채점 현황에서 여러 코드를 읽어봤습니다. 혹시 맞는 코드인데 틀린 건 아닌지, 틀린 코드인데 맞은 건 아닌지 등을 확인하기 위해서입니다. 코드를 확인하다 보니 cout << fixed를 하지 않아 문제를 아깝게 틀린 경우도 있었습니다. 안타까웠지만 어쩔 수 없었습니다.. 다행히도 틀린 코드가 통과하거나 맞은 코드가 통과하지 않는 경우는 없었습니다.

Master는 A, B, D, G, H의 5문제가, Champion은 A, B, C, D, F의 5문제가 각각 풀렸습니다. Champion 쪽은 제가 예상한 대로였으나 Master에서 문제가 많이 풀리지 않아 아쉬웠습니다. 과연 모두 재밌게 즐길 수 있을 만한 대회였을까요? Champion은 그랬던 것 같습니다. 안타깝게도 Master는 난이도 예상을 너무 잘못했던 것 같습니다.


이후 오픈 콘테스트도 순조롭게 진행되었고, 문제 오류는 딱히 없었기 때문에 바로 문제들을 공개했습니다. 제가 출제해 제일 어려운 문제로 기획했던 평행우주가 문제 공개 후 (고인물들에게) 나름대로의 인기를 끌고 있어 뿌듯합니다. 그렇다고 내년 ICPC Seoul Regional에 tree isomorphism이 나오는 걸 보고 싶진 않은데요.. 뭐 여하튼.

대회 운영과 문제 출제가 생각보다 어려운 것임을 깨닫게 해 준 계기가 되었습니다. 좋은 대회를 여는 건 정말 힘들다는 것도요. 생각해야 될 게 정말 많았습니다. 제가 했던 고민들이 대회를 여시려고 하시는 분들께 도움이 되었으면 좋겠습니다.

내년엔 아마도 회사에 가게 되기 때문에, 대회 출제는 3년 후에나 다시 하게 될 것 같습니다. 그 때는 난이도 조절에 조금 더 신경을 쓰고 싶습니다. 같이 출제해 주신 서강대학교 학우님들, 검수를 도와주신 분들과 참가자 분들께 모두 감사드립니다.

제로부터 시작했던 수험 생활

이 글은 지난 1년간 필자가 집에서 무슨 짓을 하고 살았는지에 대해 기술합니다. 무언가 많이 하긴 했습니다. 글이 상당히 깁니다.

시작

아무리 요즘 고등학교가 4년제라고는 해도 누가 고등학교 3학년 생활을 1년이나 더 하고 싶을까요? 같은 성적에 작년이었다면 분명히 최초합격할 수 있었던 학과였는데 올해 모의지원 결과를 보자니 합격 확률이 나날히 떨어지다가 결국엔 아예 가능성이 없다고 뜰 때의 절망감은 이루 말할 수 없을 정도입니다. 필자도 그랬습니다.

화학은 대체 뭐지…???

그 해 본 평가원 시험 중에서는 가장 잘 본 시험이 수능이었지만, 정작 그 수능 성적이 이랬습니다.

서울 상위권 대학을 목표로 하고 있었습니다만 여러 가지 이유로 높기만 했던 목표에 비해 고등학교 3학년 때 시험을 못 봤으며(후술), 암기로 거의 모든 게 해결되던 내신은 저에게는 정말 맞지 않아서 일반고에서 3등급 초반대를 꾸준히 유지했습니다. 물론 미적분I 같은 주요 과목에 5등급이 껴 있고 일본어나 정보 같이 자신 있던 비주요 과목이 1등급이고 하는 식으로의 3등급 초반대였습니다.

여하튼 필자는 재수를 할 자신이 전혀 없었기에 반수를 결심하고 2학기 휴학이 가능한 국립대에 지원을 합니다. 이 성적에 당연히 서울 상위권은커녕 서울 소재조차 아니었습니다. 그리고 최초합격을 합니다. 그러나..

안타깝게도 답장을 하지 못했습니다.

이 문자를 받고 마시고 취하는 대학 생활에 안주해 그냥 눌러앉아버리는 게 아닐까 – 부터 시작해서 과연 반수 생활을 제대로 할 수 있을지에 대해 여러 고민이 들기 시작했습니다. 결정적으로 친구들이 명문대학에 가는 게 정말 부러웠습니다.

그렇게 입학금을 반환받고, 돌아올 수 없는 재수의 강(재수강 아님)을 우발적으로 건너게 됩니다.

겨울

강대. 아 물론 제가 갔다는 얘기는 아니구요.

서울 지하철 2호선에는 수많은 명문대가 있습니다. 그 중 가장 빨리 졸업할 수 있는 곳이 바로 강대입니다.

물론 필자는 강남 대성학원에 갈 수 있는 성적조차 안 됐을 겁니다. 하지만 재수를 하려는 학생들은 보통 재수종합반이든 독학재수반이든 재수학원에 등록을 하고 1년간 고등학교처럼 다니는 것이 정석이므로 어디든 다니긴 해야 된다고 생각은 했습니다.

그래서 필자도 2월에 목동의 여러 재수학원을 돌아다니면서 설명을 들었습니다. 그러나 집이 그렇게 풍족하지 못한 관계로 집에서 독학을 하다가 나중에 재수학원에 등록하는 것으로 결정이 되었습니다.

언제까지일지 모를 집 독학을 시작하면서 자동적으로 인터넷 강의와 수능기출문제집을 활용한 공부를 시작하게 되었습니다. 그리고 이 때까지만 해도 단과학원을 제외하고는 11월까지 집에서 공부하게 될 줄은 몰랐습니다.

2월 말은 요즘은 절대 봄이라고 할 수 있는 날씨는 아니지만 계절과 플롯의 흐름을 통일시키기 위해 소제목을 그냥 봄이라고 합시다

재수를 시작하기 전, 17수능에서의 패인을 나름대로 구체적으로 분석했습니다. 올해는 어떤 식으로 공부를 해나가야 성적이 오르겠구나 – 라는 갈피를 잡기 위함이었습니다.

국어 과목은 원래부터 4등급 정도 나왔던 과목이기에 11월에 해던 대로만 한다면 2등급은 유지할 수 있을 것이라 생각했고, 화학도 그랬습니다. 영어도 절대평가로 전환되면서 90점이나 100점이나 같은 점수를 받게 되었고 80점대 성적을 받아도 대학에서 실질적으로 ‘까이는’ 점수가 적어서 별로 부담이 없었습니다.

문제는 수학과 생명과학이었는데, 수학은 2016년 모의고사들에서 전반적으로 쉽게 출제가 되어 왔으나 필자가 쉬운 문제에서 실수를 많이 했기에 당해 9월~11월에는 사설 봉투 모의고사를 풀면서 문제를 두 번씩 푸는 것을 연습했던 게 화근이었습니다. 17수능 수학은 절대로 쉽지 않았기에 뒷 문제를 안 보고 이런 식으로 풀어나가다가 18번을 불 때쯤 40분밖에 남지 않았던 것이었습니다. 생명과학은 지엽적인 개념을 숙지하지 않아서였습니다. 그런 이유로 수학은 일단 쉬운 문제를 빠르고 정확히 한 번에 풀 수 있는 기술을 단련해야 했고, 생명과학은 전반적으로 전체 개념을 다시 공부할 필요가 있다고 느꼈습니다.

재수생은 시간이 더럽게 많습니다. …적어도 8월 정도까지는 그렇게 느낍니다.

패인을 분석하고 나서 막상 재수를 시작해 보니 쓸 수 있는 시간이 엄청나게 많았습니다. 노트북과 스마트폰의 유혹에 쉽게 넘어가는 편이고 굳이 없애기도 귀찮아서 하루하루 공부 스케쥴을 짜기로 결심했습니다.

필자는 태어나서부터 열아홉 살때까지 ‘하루 스케쥴’이라는 것을 짜 본 적이 없었습니다. 집 독학을 시작하면서 처음으로 스케쥴을 짜 보게 되었습니다.

벽에 붙여놓고 볼펜으로 쓰기 굉장히 힘들었습니다. 하긴 계획표는 남 보여줄 건 아니니 저만 알아보면 됩니다.

아침에 일어나서 일어난 시간을 기준으로 당일 계획을 30분 단위로 짜고, 그대로 실전했습니다. 못 했을 경우엔 X표를 했고 다음 날에 전날 못 한 공부를 했습니다. 또한 공부하는 과목 순서는 실제 수능에서 응시하는 과목 순서대로 배치했습니다.

계획표를 짜 보고 나니 확실히 계획표를 짜서 공부하는 게, 피곤하거나 놀고 싶어서 계획을 조금만 짜더라도, 안 짜는 것보다 훨씬 효과적이라는 것을 알게 되었습니다.

2월에서 6월까지는 평가원 기출문제를 많이 풀고 개념을 다시 보는 위주의 공부를 했습니다. 굳이 빠르게 보려고 하지 않아도 시간이 엄청나게 많았기에 천천히 전부 두들겨봤습니다. 몰랐던 게 생각보다 엄청나게 많은 느낌이었습니다. 특히 생명과학이 그랬습니다. 개념 강의에서 교과서에 이런 내용이 나오긴 했었나 싶을 정도로 생소한 내용들이 별표와 동그라미로 등장했고, 그런 개념들은 이 때 다시 챙겨갔습니다.

3월, 4월 연합학력평가는 재수생이 고등학교 3학년 수험생(이하 ‘현역’)들과 같이 볼 수 없으니 시험이 치뤄진 다음날에 봤고, 1년 더 공부한 탓인지 한국사를 제외하고는 두 시험 모두 전부 1등급이 나왔습니다.

참고로 6월과 9월에는 학원이나 학교에서 시험을 응시할 수 있는데, 한 달 전쯤에 미리 신청해야 합니다. 응시료도 지불해야 합니다. 재수생이 현역보다 불리한 점은 수시모집을 제외하면 솔직히 이것밖에 없습니다.

여름

이 때 나태해지지 않으면 목표하는 대학은 거의 합격할 수 있을 겁니다.

국어 영역은 기분은 나쁘지만 백분위가 95.5 정도에서 반올림되면 충분히 저럴 수 있습니다.

그리고 6월 모의평가에서 쾌거를 이루게 됩니다. 수학만 빼고요. 수학 볼 때 졸았습니다. 결국 21번 29번 30번은 고사하고 18번 20번 22번(!) 26번 27번을 추가로 틀려버리는 아주 멋진 상황이 발생하게 됩니다.

그러나 ‘졸아서 4등급이었고 안 졸았으면 적어도 2등급이었을 것이다’라는 생각이 들기 시작했고, 7월에는 또 전과목 1등급을 받았습니다. 학력평가(3, 4, 7, 10월) 등급컷이 아무 의미가 없다는 걸 이 때 깨잘았어야 했는데 오히려 이 때부터 본격적으로 게을러지기 시작했습니다. 방이 더운데 에어컨이 없어서 TV가 있는 거실에 나와 공부하기 시작한 것도 한 몫 했고, 결정적으로는 실력을 자만하기 시작한 것입니다.

수험생의 적

재수생들은 다 이쯤에서 의욕을 잃는다는데 필자에게도 안 올 것 같던 시기가 그렇게 찾아왔습니다. 날마다 썼던 계획표가 점점 비었고, 어떤 날은 하루종일 휴대폰과 노트북만 붙잡고 있기도 했습니다. 나는 당장 수능을 보면 정말 잘 볼 텐데- 같은 생각을 하면서.

그래도 9월 모의고사가 다가오고 있는 것은 부정할 수가 없었으므로 슬슬 사설 봉투 모의고사를 풀기 시작했습니다. 8월 말이 되어서요.

이 수험생은 과연 9월에 어떻게 될까요? 스크롤을 내리지 말고 예측해 봅시다.

 

 

 

(미방)

 

 

 

가을

학교에 갈 낯이 없어 성적표조차 가져오지 않았습니다.

전체적으로 17수능보다 못 본 성적이 나왔습니다.

왜일까요? 6월까지 열심히 돌아본 개념이 부족했을까요? 아니면 실력이 부족했을까요?

아니었습니다. 전 과목에서 골고루 정말 말도 안 되는 실수들을 했던 것이었습니다. 틀린 16문제 대부분이 정말 터무니없는 실수들이었습니다. 어느 정도 터무니없는지 하나만 예로 들자면, 화학 I의 3번 문제에서

실선은 공유 결합, 점선은 수소 결합

분명히 실선인 결합 a를…

믿기지 않겠지만 점선으로 봐서 틀렸습니다.

어려운 문제는 다 맞아놓고(수학 21번 제외) 어떻게 남들은 일부러 틀리려고 해도 못 틀릴 문제들을 틀려놨는지 진지하게 고민해 봤고, 결국은 6~8월에 놀아서, 문제풀이에 익숙해지지 않게 되어서로 결론을 내렸습니다.

그러면 어떻게 해야 될까요? 다시 문제를 푸는 데 익숙해져야 할 것입니다. 이 시기부터는 문제 푸는 데 익숙해지기 위해, 그리고 소위 ‘킬러 문항’이라고 하는 어려운 문제들을 시간 안에 풀 수 있는 실력을 훈련하기 위해 봉투모의고사 위주의 공부를 하기 시작했습니다. 기출을 풀면서 킬러 문항도 접해봤겠지만, 그냥 ‘풀 수 있는 것’과 ‘시간 안에 풀 수 있는 것’은 다르다는 것을 느꼈습니다. 결국 수능은 시간 싸움이니까요.

또한 단과학원을 수강하게 되었습니다. 대치동이나 목동, 분당의 단과학원들은 재수학원보다 가격이 쌀 뿐더러 잘만 수강한다면 상당히 양질의 퀄리티의 사설 문제들을 매 주 풀어볼 수 있다는 장점이 있습니다. 재수생 자체가 단과학원에 잘 보이지 않기에 뭔지 모를 이질감이 들 수 있으나 뭐 3달만 다니면 되니까요.

아 수시는 묻지 말아주세요. 6개 모두 논술 썼습니다. 6군데 모두 떨어졌습니다. 감사합니다.

여담으로, 불안감은 사용함에 따라서 장애물이 될 수도 있고, 연료가 될 수도 있습니다. 정확히는 불안감과 확신이 균형잡혀 있을 때 최고의 연료가 됩니다. 당시 9월 모의고사를 보고 과연 수능은 작년보다 잘 보긴 할지 엄청나게 불안했는데요, 불안하기만 했다면 과연 제가 수능에서 긴장하지 않을 수 있었을지 모르겠습니다. 확실한 건 2월~6월에 착실하게 공부했더니 6월 모의고사 성적은 17수능에서 평균 3등급을 받았다고는 믿겨지지 않을 정도로 좋았었다는 것입니다(수학 제외). 하면, 됩니다. 9월은 안 해서 안 된 것이었습니다.

그렇게 수능까지 전력으로 달렸습니다.

수능 연기

연기 발표 당일에는 이 짓을 무려 7일이나 더 해야 된다는 사실에 절망하고 분노했습니다. 하지만 자연재해는 누군가의 탓을 할 수 있는 건 아니기에 화는 나지만 그냥 필자가 지구과학을 선택하지 않은 대가 중 하나라고 생각하고 있습니다.

탐구는 단기간에 성적을 올리기 좋은 과목입니다. 7일 동안엔 탐구 공부에 비중을 뒀습니다.

수능 당일

제가 여기서 본 건 아닙니다.

18수능은 17수능과 같은 시험장에서 보게 되었습니다. 당일 일찍 일어나 아침 5시 반에 출발해 6시에 일찍 도착해서 차에서 조금 졸다가, 7시 반에 교실에 입실했습니다. 아침은 속이 편하면서도 열량 높은 바나나 하나로 해결하고, 막대한 양의 초콜릿을 가져가 한 교시 한 교시 끝날 때마다 먹었습니다.

수능 당일 컨디션과 멘탈 관리는 정말 중요합니다. 당장 1교시 국어 첫 장에 적힌 글이 전혀 눈에 들어오지 않았습니다. 특히 1교시에 다른 수험생들이 1페이지를 다 풀고 나서 종잇장 넘기는 소리는 정말 신경쓰이는데요, 그래서 저는 그냥 2페이지부터 풀고 나서 1페이지를 풀었습니다. 그러면 종잇장 소리에 신경쓸 필요가 없어지니까요.

초콜릿은 살 안 쪄요. 살은 제가 쪄요.

점심은 평소에 먹는 대로, 피곤하지 않을 정도로 조금 적게 먹었습니다. 어차피 초콜릿이 열량을 다 해결해 줍니다. 초콜릿은 맛있습니다. 뭐 그 날 하루 많이 먹는다고 해서 살이 막 몇 kg씩 불어나는 것도 아니고 머리께서 잘 회전하시겠다는데 당연히 먹어줘야죠 어쩔 도리가 있겠습니까.

전체적으로 어렵긴 했지만, 국어 1페이지를 빼놓고는 그렇게 긴장하지 않았던 시험이었던 것 같습니다. 수능 직전까지 실전까지 연습하다 보면 분명히 본수능인데도 모의고사 푸는 느낌이 납니다. 분명히 모의고사 푸는 거 같은데 시험지 표시에 ‘모의고사’라는 글자가 없을 뿐입니다.

한 과목 한 과목 끝나면 과연 내가 맞게 풀었을까 하는 불안감이 들기도 하지만, 어차피 내 버린 OMR 그냥 잊고 편하게 다음 과목 봤습니다. 물론 쉬는 시간엔 굉장히 걱정했습니다.

수험표 뒤에 붙이는 답안지는 최대한 채워 오는 것이 좋습니다. 성적표 나오기 전까지의 정신건강에요.

탐구 영역까지 모든 시험이 끝나고 집에 돌아가는 길은 작년보다 홀가분했습니다.

다시 겨울

집에 돌아와서 떨리는 마음으로 저녁에 가채점을 해 봤습니다. 제 성적이라고는 믿을 수 없는 성적이었습니다. 다행스럽게도 이번엔 좋은 쪽이었습니다.

실감이 잘 나지 않았습니다. 수학에서 OMR 카드를 수정테이프로 몇 번씩 지우고 고치고 했는데 혹시나 테이프가 떨어지지는 않았을까, 잉크가 번지진 않았을까 하는 걱정에 성적발표 전날까지 불안했습니다.

그러나 쓸데없는 걱정이었습니다.

한국사를 제외하고 17수능에 비해 여덟 등급이 올랐습니다.

여름에 해이해지지 않았으면 전부 1등급도 가능하지 않았을까 하는 아쉬움도 남았지만, 그래도 정말 기뻤습니다. ‘재수하길 잘했어’를 되뇌었습니다.

노력해서 무언가 제대로 이루어본 적은 없었기에 의지를 갖고 생각하는 대로만 하면 이루어진다는 것을 배우게 됐다는 점에서 2017년 한 해는 지금까지 살아온 인생 중에서 분명 필자에게 가장 의미있었던 해였습니다.

지금은 정시 원서를 접수하고 결과를 기다리고 있습니다. 잘 될 것 같습니다.

작년엔 이렇게 살았습니다.