冷めたコーヒー

Weniger, aber besser

AtCoder ABC 150 C - Count Order (300 点) in Python

問題概要

大きさ $N$ の順列*1 $P,\,Q$ が与えられる.このとき

  • $P$ が辞書順で何番目か
  • $Q$ が辞書順で何番目か

をそれぞれ求め,それらの差の絶対値を求めよ.

問題へのリンク

制約

  • $2 \leqq N \leqq 8$
  • $P,\,Q,\,N \in \mathbb{N}$

思考プロセス

  • $N$ は高々 $8$ なので,愚直に $N!$ 通りの順列を計算しても大丈夫そう.
  • でも,もっと賢い方法を考えたい.そういえば,ProjectEuler 24 と似ている.
  • 例えば,$(3,4,1,2)$ を考えると,最上位が $3$ であるため,最上位が $1$ と $2$ のそれぞれ $3!=6$ 通り(計12通り)は排除できる.次に,最上位から2番目の桁を見ると $4$ なので,$31$xx および $32$xx のそれぞれ $2!=2$ 通り(計4通り)は排除できる.そして,最上位から $3$ 番目を見ると $1$ なので,$3412$ の可能性しかなくなり,順番が確定する.よって,$(3,4,1,2)$ は $12+4+0+1=17$ 番目の順列である*2.このようにして,与えられた順列が辞書式で何番目なのかを計算することができる.
  • 注意すべき点としては,例えば $(2,4,3,1)$ の場合を考えたとき,最初に $(2-1)\times 3! = 6$ 通りの可能性が排除される.次に,$(4-1)\times 2! = 6$ 通りの可能性が排除されるかというとそうではなく,正しくは $2\times 2!=4$ 通りの可能性が排除される.これは,最上位の桁が $2$ であり,$21$xx または $23$xx の可能性しかないためである.

書いたコード(Python

def fact(n):
    if n==1 or n==0:
        return 1
    else:
        return n*fact(n-1)

def index(n,lis):
    for i in range(len(lis)):
        if n==lis[i]:
            return i

def count(lis):
    l = list(range(1,len(lis)+1)) #[1,2,...,n]
    ret = 1
    for i in range(len(lis)-1):
        ret += (index(int(lis[i]),l)) * fact(len(l)-1)
        l.remove(lis[i])
    return ret

動作例

>>> count([4,1,3,2])
20

*1:$1,2,\dots,N$ を並び替えてできる数列.例えば,大きさ $3$ の順列は,$(1,2,3),\,(1,3,2),,\,(2,1,3),\,(2,3,1,),\,(3,1,2),\,(3,2,1)$ の計 $3!=6$ 通りある.

*2:足し合わせた数に $1$ を加えた数が順番であることに注意.