KLab主催 天下一プログラマー 第3回予選第3問

 土曜日に参加した。3問目が時間内に解けなかったのが悔しかったので、とりあえずコードを仕上げた。使用言語はPython。psyco付きだと10秒かからない。

 アルゴリズムは次の通り。タイルの敷き詰め問題っぽいので、正方形のあれはタイルって呼んでます。

  • for k in 1...タイルの総数
    1. k個のタイルの選び方の組を全て出す
    2. 面積の総和が900を越える組は省く
      • 組が残ってない場合、終了
    3. タイルの良し悪しにより、考慮しなくていい組を省く
      • これが肝で、ほとんど減らせる
    4. 価格の総和が現在得られている最大値以下であるのも省く
    5. 残った組を、価格の総和が大きい順から以下の手順
      • その組で敷き詰められるかを判定
          • yes なら最大値を更新して次の k に
          • no なら次の組へ

詳しくは以下のコードを見てください。


追記:判定関数に簡単な枝刈りを追加したら1秒くらいになった。

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import psyco
psyco.full()

tiles = [
    ('a',   (9, 13),   69),
    ('b',   (9, 24),   24),
    ('c',   (15, 23),  59),
    ('d',   (6, 26),   50),
    ('e',   (7, 9),    80),
    ('f',   (8, 16),   42),
    ('g',   (7, 23),   33),
    ('h',   (6, 17),   67),
    ('i',   (6, 11),   42),
    ('j',   (11, 13),  51),
    ('k',   (14, 26),  31),
    ('l',   (13, 26),  22),
    ('m',   (7, 11),   60),
    ('n',   (10, 24),  76),
    ('o',   (7, 17),   54),
    ('p',   (13, 14),  44),
    ('q',   (13, 22),  61),
    ('r',   (13, 20),  29),
    ('s',   (11, 11),  62),
    ('t',   (11, 22),  48),
    ]

    
def print_d(a, n):
    d = [['_']*n for _ in xrange(n)]
    for i in a:
        fill_d(d, i[0], i[1], i[2], i[3], i[4])

    print '-'*34
    for r in d:
        print ''.join(r)
    print

def fill_d(d, char, r0, c0, r1, c1):
    for r in xrange(r0, r1):
        for c in xrange(c0, c1):
            if d[r][c] != '_':
                d[r][c] = '*'
            else:
                d[r][c] = char

def new_sealine(sealine, s, r, c, n):
    assert 0 <= s < len(sealine)

    r0, c0 = sealine[s]
    r1, c1 = r0+r, c0+c

    if not (r1<=n and c1<=n):
        return []

    left = []
    if r1<n:
        for i in xrange(s-1,-1,-1):
            R, C = sealine[i]
            if R==r1:
                left = sealine[:i+1]
                break
            elif R>r1:
                left = sealine[:i+1]+[(r1, sealine[i+1][1])]
                break
        else:
            left = [(r1, sealine[0][1])]
    right = []
    if c1<n:
        for i in xrange(s+1,len(sealine)):
            R, C = sealine[i]
            if C==c1:
                right = sealine[i:]
                break
            elif C>c1:
                right = [(sealine[i-1][0], c1)]+sealine[i:]
                break
        else:
            right = [(sealine[-1][0], c1)]
    return left + right

def find_fill(tiles, n):
    m   = len(tiles)
    tbl = {}

    stack = []
    #first = []

    def dp(sealine, bit):        
        if bit == (1<<m)-1:
            return True

        key = tuple(sealine+[bit])
        if key in tbl:
            return False

        tbl[key] = 1

        # 
        # if first and bit&1==0 and first[0]&bit == first[0]:
        #     return False

        # ある特定のタイルが貼れない
        need_area = 0
        for i in xrange(m):
            if bit&(1<<i) == 0:
                (char, (r, c), v) = tiles[i]
                for r0, c0 in sealine:
                    if ((r0+r<=n and c0+c<=n) or
                        (r0+c<=n and c0+r<=n)):
                        break
                else:
                    return False
                need_area += r*c
        # 面積が足りない
        area = 0
        for s in xrange(1,len(sealine)):
            area += (sealine[s][1]-sealine[s-1][1]) * (30-sealine[s-1][0])
        area += (30-sealine[-1][1]) * (30-sealine[-1][0])

        if area < need_area:
            return False


        for i in xrange(m):
            if bit&(1<<i) == 0:
                (char, (r, c), v) = tiles[i]

                for s in xrange(0,len(sealine)):
                    for r, c in ((r, c), (c, r)) if bit else ((r, c),):
                        next = new_sealine(sealine, s, r, c, n)
                        if next:
                            r0, c0 = sealine[s]
                            stack.append((char, r0, c0, r0+r, c0+c))

                            # if not bit:
                            #     first.append((1<<m) - (1<<i))

                            if dp(next, bit | (1<<i)):
                                return True

                            # if not bit:
                            #     first.pop()

                            stack.pop()
        return False


    r=dp([(0,0)], 0)
    return stack if r else None
                        
# 良いタイルと悪いタイルの比較
def tile_relation(tiles):
    def strict_better(a, b):
        return a[2] > b[2] and a[1][0] <= b[1][0] and a[1][1] <= b[1][1]

    m = len(tiles)

    better = [set() for _ in xrange(m)]
    weaker = [set() for _ in xrange(m)]

    for i in xrange(m):
        for j in xrange(m):
            if i!=j and strict_better(tiles[i], tiles[j]):

                better[j].add(i)
                weaker[i].add(j)
    # 推包を求める
    for _ in xrange(m):
        for i in xrange(m):
            s = better[i]
            for j in better[i]: s |= better[j]
            better[i] = s
            s = weaker[i]
            for j in weaker[i]: s |= weaker[j]
            weaker[i] = s
            
    return better, weaker


# n から k 個選ぶ
def pick_combination(tiles, k, better, weaker):
    n = len(tiles)

    def gen(i, t, must, must_not):
        if n-i+len(t) < k or k < len(t)+len(must-set(t)):
            return
        if must & must_not:
            return
        if len(t) == k:
            if not must - set(t):
                yield t
            return
        # i を含める
        if i not in must_not:
            for combi in gen(i+1, t+[i], must|better[i], must_not):
                yield combi        
        
        # i を含めない
        if i not in must:
            for combi in gen(i+1, t, must, must_not|weaker[i]):
                yield combi        

    return list(gen(0, [], set(), set()))

def main():
    n = 30
    d = [['_']*n for _ in xrange(n)]

    better, weaker = tile_relation(tiles)

    max_alignment = []
    max_value = 0
    for k in xrange(1,len(tiles)):
        print 'Using', k, 'tiles'

        combis = [[tiles[i] for i in c]
                  for c in pick_combination(tiles,k,better,weaker)]
        combis = [c for c in combis
                  if sum(i[1][0]*i[1][1] for i in c) <= n*n]

        if not combis:
            print "Can't place", k, "tiles"
            break

        combis = [(sum(x[2] for x in t), t) for t in combis]
        combis.sort()
        combis.reverse()

        for value, combi in combis:
            if value <= max_value:
                break

            print 'try ...',value, combi

            a = find_fill(combi,n)
            if a:
                max_value = value
                max_alignment = a
                break
        print 'Current max value is', max_value

    print max_value
    print_d(max_alignment, n)


if __name__=="__main__":
    main()

答え

485

aaaaaaaaaaaaahhhhhhhhhhhhhhhhh
aaaaaaaaaaaaahhhhhhhhhhhhhhhhh
aaaaaaaaaaaaahhhhhhhhhhhhhhhhh
aaaaaaaaaaaaahhhhhhhhhhhhhhhhh
aaaaaaaaaaaaahhhhhhhhhhhhhhhhh
aaaaaaaaaaaaahhhhhhhhhhhhhhhhh
aaaaaaaaaaaaa_____jjjjjjjjjjj_
aaaaaaaaaaaaa_____jjjjjjjjjjj_
aaaaaaaaaaaaa_____jjjjjjjjjjj_
eeeeeeeee__ooooooojjjjjjjjjjj_
eeeeeeeee__ooooooojjjjjjjjjjj_
eeeeeeeee__ooooooojjjjjjjjjjj_
eeeeeeeee__ooooooojjjjjjjjjjj_
eeeeeeeee__ooooooojjjjjjjjjjj_
eeeeeeeee__ooooooojjjjjjjjjjj_
eeeeeeeee__ooooooojjjjjjjjjjj_
iiiiiiiiiiiooooooojjjjjjjjjjj_
iiiiiiiiiiiooooooojjjjjjjjjjj_
iiiiiiiiiiiooooooojjjjjjjjjjj_
iiiiiiiiiiiooooooosssssssssss_
iiiiiiiiiiiooooooosssssssssss_
iiiiiiiiiiiooooooosssssssssss_
mmmmmmmmmmmooooooosssssssssss_
mmmmmmmmmmmooooooosssssssssss_
mmmmmmmmmmmooooooosssssssssss_
mmmmmmmmmmmooooooosssssssssss_
mmmmmmmmmmm_______sssssssssss_
mmmmmmmmmmm_______sssssssssss_
mmmmmmmmmmm_______sssssssssss_
__________________sssssssssss_

ムカデ

 今年もムカデの季節がやってきました。奴らはとっても凶悪。生命力が高く、見た目が気持ち悪く、咬まれるとめっさ痛い。奴らに比べたらゴキブリなんて友達になってもいいと思うほど凶悪。そんな奴らが部屋に忍び込むのがこの季節。PCいじってたらいきなり腕に這い上がってきたり、布団の中に入り込んだり、靴の中に入り込んだり、上から落ちてきたり。毎年最低一回は咬まれてる気がする。お子さん(10cm以下)ならまだしも親御さん(15cm以上)に咬まれたら死んじゃいそう。

 というわけで以下注意してること。

  • 部屋に帰ったら、まず息を潜めて耳を澄ませる
    • 奴らの弱点は移動時に音を立てること
  • 定期的に回りを見回す
  • 洗濯物や本など物を持ち上げるときは覚悟を持って行う
  • 寝る前に布団の中をチェック
  • 靴を履く前にチェック
  • 殺虫剤の常備
    • 強力な奴を。今置いてるやつは「名前の分からない虫にも効く」と書いてある。

みなさんも気をつけましょう。

Ubuntu 9.04

 8.10から9.04にアップグレードしました。8.04から8.10に上げた時、perl5.8がもってかれて酷い目にあったので、今回はバックアップをしてやった。結局今回は大丈夫だったんだけど。
 ぱっと見あまり変わってない感じ。起動が若干速くなったかな。英字フォントが細くなった気がする。ちょっと違和感あるから戻したい。デュアルディスプレイの設定が簡単になってるのはうれしいな。

Circle of Numbers

 試しに作ってみた問題その1。

問題

 0からn-1のn個の数字を時計回りで輪に並べます。最初0から出発して右回りにk番目の数字を輪から外します。外した数字の次にある数字からまたk番目の数字を輪からはずします。これを繰り替えしていったとき最後に残る数字はなんでしょうか、という問題です。

 例えば n=6, k=3 の場合を見ると、

    0
  5   1
  4   2
    3

0, 1, 2: 2を輪から外す
  
    0
  5   1
  4   x
    3

3, 4, 5: 5を輪から外す

    0
  x   1
  4   x
    3

0, 1, 3: 3を輪から外す

    0
  x   1
  4   x
    x

4, 0, 1: 1を輪から外す

    0
  x   x
  4   x
    x

4, 0, 4: 4を輪から外す

    0
  x   x
  x   x
    x

この場合最後に残る数字は0となります。他に例を挙げると、

n = 30, k = 6 の場合 3
n = 64, k = 11 の場合 48
n = 64, k = 128 の場合 57

になります。では、以下に与えられるn、kの場合、最後に残る数字は何になるでしょうか。

n = 10^2, k = 9
n = 10^4, k = 123
n = 10^6, k = 1024
n = 10^8, k = 512
n = 10^10, k = 255