AT274の技術だったりなかったり

あっとのTECH LOG

競プロのこととか技術のこととか。たまに日常を書きます。

JOI難易度6 アナグラム

問題原文

https://www.ioi-jp.org/camp/2007/2007-sp-tasks/2007-sp-day3_23.pdf

問題要旨

文字列  S が与えられる。
 S の全てのアナグラムのうち、  S は辞書順で何番目か?
ただし、   S Sアナグラムとする。

  •   1 \leq |S| \leq 20

解法

全通り並べることはできないので、うまく数える必要がある。

sample1 の  S = HEART をもとに考えることにする。
まず、先頭の文字が A または E ならば、必ず HEART より小さいことになる。
そしてそのようなものは、

  • 先頭が A → 残ったEHRT の並べ方が  4! 通り
  • 先頭が E → 残った AHRT の並べ方が   4! 通り

で、48通りある。
これで、「1文字目までの段階で  S よりも辞書順で小さくなるものの通り数」が出せた。
これを2文字目。。。3文字目。。。とやっていくと、答えがでる。

 S  = CROATIA のようなケースでは、 C より小さい A が2つあるが、それぞれのパターンを別のものとしてカウントしないように注意すること。

実装

煩雑になりそうだが、冷静になると短くかける。
 X の並べ方の通り数をメソッドとして外だししておくとデバッグが楽。

from collections import Counter
from math import factorial
S = list(input())

def calc(X):
    ret = factorial(len(X))
    for v in Counter(X).values():
        ret //= factorial(v)
    return ret

ans = 0
for i, s in enumerate(S):
    used = set()
    for j, t in enumerate(S[i:], start=i):
        if (t < s) and (t not in used):
            ans += calc(S[i: j] + S[j + 1:])
            used.add(t)

print(ans + 1)

感想

考察というよりは実装よりの問題。 はぁ〜〜〜〜〜〜めんどくせ〜〜〜〜と思って放置してたけどそうでもなかった。
CROATIA にはひっかかった(は?)