くふむ

けふぇめ

WBC004 C - Blurred Numbers 解説

2021年3月18日に開かれたウインドベル(@Twi_Stamp)主催の競プロ有志コンテストWind Bel Contest 004 – Good Bye 2A Day 2の解説です。

問題文

文字列 S が与えられます。S の各文字は、ローマ数字の記号 (I, V, X, L, C, D, M) または ? です。

? をローマ数字の記号に置き換えてできる文字列のうち、文字列全体を通してローマ数字として成り立つものは何通りあるでしょうか?

しかし、東京大学には「優3割規定」というルールがあります。「優3割規定」とは、『 80 点以上の成績がつく学生の人数は受講者全体の 30% 以下でなければならない』というルールです。このルールに違反すると、怒られが発生します。

怒られるのは嫌なので、あなたは「優3割規定」に違反しないように成績をつけ直すことにしました。あなたは任意の学生の点数を 79 点に変えることができます。最低で何人の学生の点数を 79 点に変えれば「優3割規定」をみたすことができるか求めてください。

制約

  • S はローマ数字の記号 (I, V, X, L, C, D, M) と ? からなる
  • 1 \leq |S| \leq 20

解説

桁DPはしません。この問題、メチャクチャ見掛け倒しです。

作れるローマ数字は 1 以上 3999 以下のたった 4000 通りなので、ローマ数字のほうを全部生成して文字列 S に一致するものがいくつあるか数えればよいです。

計算量は N\leq 4000 として \mathrm{O}(N|S|) です。

実装例

def int_to_roman(n):
    """
    1~3999のアラビア数字を入れるとそれに対応するローマ数字を返す関数
    """
    assert 1 <= n <= 3999

    res = ""

    # 1000の位
    d, n = divmod(n, 1000)
    res += ["", "M", "MM", "MMM"][d]

    # 100の位
    d, n = divmod(n, 100)
    res += ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"][d]

    # 10の位
    d, n = divmod(n, 10)
    res += ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"][d]

    # 1の位
    res += ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"][n]

    return res

def is_match(s, target):
    """
    与えられた文字列Sが目的のローマ数字を表せるか判定する関数
    """
    # 長さが違ったらダメ
    if len(s) != len(target):
        return False
        
    # 1文字ずつ見ていって食い違いがあったらダメ
    for sl, tl in zip(s, target):
        if sl != tl and sl != "?":
            return False
    return True

def solve():
    s = input()
    print(sum(is_match(s, int_to_roman(n)) for n in range(1, 4000)))
    
t = int(input())
for _ in range(t): solve()

ひとこと

最近、ABCのCにもこういう問題多いですよね。