JMK no matter what

GCJ 기출문제 정리 #3

정리1, 정리2 에 이어, 정리를 빙자한 립코딩.

2009년 라운드 3

A. EZ-Sokoban

별다른 트릭 없이도 상태 공간 탐색으로 풀 수 있다. 최대 상태의 크기는 144^5 같지만 물론 이것보다 훨씬 작다. 대회 당시에도 그렇게 무리없이 풀었다.

B. Alphabetomials

당시엔 이지만 무식하게 돌려서 풀었던 듯싶다. 한참 고민해서 결국 풀긴 풀었다. thought process 를 대충 적어보면..

  1. 모든 p(S) 의 합을 계산하는 것인데, 각 텀별로 따로 계산하고 그 값을 더해도 된다는 것을 아는 것이 첫 번째 포인트.
  2. 이렇게 저렇게 부분 문제를 생각해 보려 했지만 별로 잘 되지 않았다. 가장 먼저 생각해본 건 C[k][S] = k개 골랐고, 각 글자의 출현 회수의 집합이 S 일 때.. 로 부분 문제를 잡아서 동적 계획법을 돌리는 건데. S 의 크기가 너무 커서 infeasible.
  3. 다른 방향으로 간단하게 풀어보면.. K=1 이면 쉽게 풀 수 있다. 다 돌아보면서 해 볼 수 있으니까. 이 경우의 답을 갖고 있다고 하자. K=2 를 쉽게 풀 수 있을까? 다 안해보고?
  4. 결국 답의 실마리를 발견한 것은 작은 예제에 대해 손으로 전개해 보는 것이었다. 여기서 내가 ab 와 cd 를 알고 있다고 하자. 이 때 (a+c)(b+d) 를 쉽게 알 수 있을까? 전개해 보면 ac+ad+bc+bd 가 나온다. abc 와 def 가 있으면 (a+d)(b+e)(c+f) 는 abc+abf+aec+aef+... 2^3 개의 텀으로 나온다.
  5. 좀더 일반화한 한두개에 대해서 해보자. aab 와 ccd 를 알고 있을 때 (a+c)(a+c)(b+d) 도 같은 꼴로 나온다. 당연하지만..
  6. 그러면 모든 단어 조합에 대해서 aab, ab, b, aa, 1 의 합을 알고 있으면 쉽게 둘을 합칠 수 있겠네.
  7. 남은 건 이게 여러 조합의 합이 있을 때 가능한가 한 것인데.. 이건 비교적 쉽게 증명한거 같다.
  8. 여기까지 와서 손으로 작은 거 하나 돌려보고 코딩했다.
lang:py
#!/usr/bin/python
import sys
rl = lambda: sys.stdin.readline().strip()

M = 10009

def solve(term, words):
    chars = len(term)
    c = [[0] * (2**chars) for i in range(k+1)]
    # fill c[1]
    for subset in range(2**chars):
        for w in words:
            mult = 1
            for i in range(chars):
                if subset & (2**i):
                    mult *= w.count(term[i])
            c[1][subset] += mult
            c[1][subset] %= M
    for i in range(2,k+1):
        for subset in range(2**chars):
            for subsubset in range(2**chars):
                if (subset & subsubset) != subsubset:
                    continue
                a = c[i-1][subsubset]
                b = c[1][subset - subsubset]
                c[i][subset] += a*b
                c[i][subset] %= M

    total_set = (2**chars)-1
    return [c[i][total_set] for i in range(0,k+1)]

def brute(term, words):
    sol = [0] * (k+1)
    cnt = [0] * len(term)

    def recur(selected, cnt):
        m = reduce(lambda a, b: a*b, cnt)
        sol[selected] += m
        if selected < k:
            for w in words:
                next_cnt = list(cnt)
                for i in range(len(term)):
                    next_cnt[i] += w.count(term[i])
                recur(selected+1, next_cnt)

    recur(0, cnt)
    return sol

t = int(rl())
for cc in range(t):
    expr, k = rl().split()
    k = int(k)
    expr = expr.split("+")
    n = int(rl())
    words = [rl() for i in range(n)]
    ret = [0] * (k+1)
    for term in expr:
        #sol = brute(term, words)
        sol = solve(term, words)
        for i in range(k+1):
            ret[i] = (ret[i] + sol[i]) % M

    print "Case #%d: %s" % (cc+1, " ".join(map(str, ret[1:])))
    sys.stderr.write("did %d\n" % (cc+1))

결국 해결점은 작은 예제를 손으로 해 보면서 얻을 수 있었다.

C. Football Team

그래프를 그려보니 어? 이거 평면그래프네. 그럼 4컬러링은 무조건 되고 3컬러링만 하면 되겠구나. 싶었는데 컬러링 되나를 어떻게 알지.. 평면그래프 3컬러링도 np 컴플릿일텐데... 하고 orz 실제로 푼 사람들을 보니 bmerry 는 무려 그리디고 (헐, 이게 되나?) 백트래킹도 있고.. 다양하다.

easy 를 스위핑 하면서 dp 로 풀어볼까 했는데 어려워서 포기했는데, 오른쪽에서 왼쪽으로 쓸면 된단다. 아 맞다! 완전 천재 orz

한번 쭉 훑어보면서 괜찮은 솔루션을 배워봐야 할 듯.

D 는 이지를 당시에 풀긴 했지만 들여다 보지도 않았따. -_-

2010-06-09 12:35:22 | JM | /logs/ | 0 개의 댓글들

댓글 남기기