Branched Evolution

Competitive Programming in Python

ジェネレーターによる与えられた条件をみたす数列の全列挙

概要

与えられた条件をみたす数列を全列挙したいとき、ジェネレーターを使うと簡単に書けて便利。

ジェネレーターとは

イテレータを生成してくれる関数。return の代わりに yield を使って値を返す。 詳しくは公式ドキュメントを参照されたい。呼ばれる度に次の値を返す。
例えば、

def generate():
    yield 0
    yield 1
    yield 2

で定義すると、

for a in generate():
    print(a)

のように使うことができる。

順列の列挙

$1$ から $n$ までの自然数から $ r $ 個を選んで並べてできる数列をすべて列挙したいとき、Python なら itertools ライブラリを使って

import itertools
for A in itertools.permutations(range(1, 6 + 1), 3):
    print(A)

と書くことができるが、ジェネレーターを使って、

def generate(itr, r):
    if r == 0:
        yield []
    else:
        for A in generate(itr, r - 1):
            for i in itr:
                if i not in A:
                    yield A + [i]

for A in generate(range(1, 6 + 1), 3):
    print(A)

と書くこともできる。これは以下のように考えればよい。
$1$ から $n$ までの自然数から $ r $ 個を選んで並べてできる数列というのは、$1$ から $n$ までの自然数からなる長さ $r$ の数列で、重複がないものである。
ここで、$1$ から $n$ までの自然数からなる長さ $r - 1$ の数列がすべて列挙できたとすると、それぞれの数列 $A$ の末尾に $A$ に現れない要素を追加した数列を列挙すればよい。

8クイーン問題

チェス盤に $8$ 個のクイーンを、どのクイーンも他のクイーンに取られないように置く配置を全て求めたい。
この問題を言い換えると、$0, \ldots, 7$ からなる長さ $8$ の数列 $A$ で、$i \neq j$ ならば $A _ i \neq A _ j$ かつ $| A _ i - A _ j | \neq | i - j |$ をみたすものを求めるということになり、以下のように書ける。

def generate(n=8, m=8):
    if n == 0:
        yield []
    else:
        for A in generate_queen(n - 1, m):
            for b in range(m):
                flag = True
                for i, a in enumerate(A):
                    if a == b or abs(a - b) == abs(i - (n - 1)):
                        flag = False
                if flag:
                    yield A + [b]

$0, \ldots, 7$ からなる長さ $n - 1$ で条件をみたす数列 $A$ に対して、その末尾に $b$ を追加したいとき、各 $a \in A$ とその位置 $i$ について $a=b$ または $|a-b| = | i - (n - 1) |$ となっていれば追加できず、そうでない場合に追加することで長さ $n - 1$ で条件をみたす配列ができる。

Panasonic 2020 D - String Equivalence

長さ $n$ の標準形は、長さ $n - 1$ の標準形 $S$ に、$S$ に含まれる文字か $S$ に含まれない文字のうち辞書順で最初のものを付けたものなので、数列に言い換えると、長さ $n$ の数列は、長さ $n - 1$ の数列 $A$ に $0$ から $\mathrm{max}\ A + 1$ までの数字を追加してできるものなので、以下のように書ける。

def generate(n):
    if n == 1:
        yield [0]
    else:
        for A in generate(n - 1):
            for i in range(max(A) + 2):
                yield A + [i]

ABC 161 D - Lunlun Number

先頭は $0$ でなく、$0$ から $9$ の数字からなる長さ $n$ で隣り合う数字の差が $1$ 以下の数列を列挙したい。条件をみたす長さ $n - 1$ の数列 $A$ に対して、$A$ の末尾を $x$ とすると、$|x - i| \leq 1$ なる $i$ を $A$ に追加してできる数列が条件をみたす長さ $n$ の数列である。

def generate(n):
    if n == 1:
        for i in range(1, 10):
            yield [i]
    else:
        for A in generate(n - 1):
            for i in range(10):
                if abs(A[-1] - i) <= 1:
                    yield A + [i]