AtCoder NoviStepsを埋めてみる(4) bit全探索の続きです。今回は順列全探索です。

C++ であれば標準ライブラリである next_permutation 関数を用いて順列全探索を行うことができます。ただ C# にはこのようなものがないそうです。しかし ac-library-csharp というライブラリには同じようなメソッドがあります。今回はこれを使い倒すことにします。

参考: ac-library-csharpを使ってみる

StlFunction.NextPermutationメソッドの使い方は C++ の next_permutation 関数 とほぼ同じです。

C – Count Order

C – Count Order

問題の概要

(1, 2, …, N) を並び替えてできる数列 P, Q がある。
N の順列は N! 通り考えられるが、このうち P が辞書順で a 番目に小さく、Q が辞書順で b 番目に小さいとして |a – b| の値は?

StlFunction.NextPermutationメソッドで順列を求め、数列 P, Q が何番目の順列かを調べればよいです。

C – Average Length

C – Average Length

問題の概要

座標平面上に N 個の町(座標は (x[i] , y[i]))がある。
これらの町をすべて 1 回ずつ訪れるとき、その移動距離の総和の平均値を計算せよ。

N 個の町を並べ替えた順列を生成して出発点から終着点までの移動距離の総和を計算してその平均値を求めればよいです。

C – One More aab aba baa

C – One More aab aba baa

与えられた文字を配列に格納して StlFunction.NextPermutation メソッドを使えばよいです。文字も整数の配列と同じように使えます。先頭の順列から何番目かを求めなければならないので入力された文字列をソートしておく必要があります。

C – Select Mul

C – Select Mul

問題の概要

整数 N が与えられる。
N の各桁の数字を取り出して並べ(並べる順序は好きに変えてよい)、2 つの正整数に分離する。
ただし、分離されたあとの 2 整数に leading zero が含まれていてはならない。
分離後の 2 数の積の最大値は?

整数 N を文字列とみなしてその順列を考えます。2 数にわけるときはわけることがすべての部分を考えます。2 数にわけたらその積を計算して最大値を求めます。

032 – AtCoder Ekiden(★3)

032 – AtCoder Ekiden(★3)

問題の概要

N 人の駅伝選手がいる。1 人の選手が 1 つの区のみを担当して走る。
選手 i が第 j 区を走るのにかかる時間は A[i, j] である。
「選手 X[i] と Y[i] は仲が悪い」という M 個の噂がある。
仲が悪い選手のあいだではタスキの受け渡しができないという条件でゴールするまでにかかる時間の最小値を求めよ。

選手が走る順番をすべて調べます。仲が悪い選手が並ぶ順番であればそれを除外してゴールするまでにかかる時間の最小値を調べます。

C – One-stroke Path

C – One-stroke Path

問題の概要

自己ループと二重辺を含まない N 頂点 M 辺の重み無し無向グラフが与えられる。
頂点 1 を始点として、全ての頂点を1度だけ訪れるパスは何通りあるか数えよ。

順列を生成して実際にその順にたどることができるかどうか調べてできるのであればカウントします。

C – Avoid K Palindrome 2

C – Avoid K Palindrome 2

問題の概要

S の文字を並び替えて得られる文字列(S 自身を含む)であって、長さ K の回文を部分文字列として含まないものの個数を求めよ。

順列をつかって S の文字を並び替えて得られる文字列を生成して、長さ K の部分文字列を生成してそのなかに回文が存在しないものを数え上げるだけの問題です。

C – Graph Isomorphism

C – Graph Isomorphism

問題の概要

2つの単純無向グラフが与えられます。ふたつのグラフが合同かどうか調べよ。

グラフが合同かどうかは順列をつかって片方の頂点の位置を入れ替えて対応する頂点が同じ頂点とつながっているものがあるかどうかを調べます。

C – Make Isomorphic

C – Make Isomorphic

問題の概要

ふたつの単純無向グラフ G, H が与えられる。
A[i, j] 円を支払って、頂点 i と頂点 j を結ぶ辺がなければ追加し、あれば取り除く。
G と H を同型にするために少なくとも何円支払う必要があるだろうか?

順列をつかってグラフ H の頂点の位置を入れ替え、対応する辺が一致するようにするために必要なコストを調べ、最小になるものを調べます。

D – Laser Marking

D – Laser Marking

問題の概要

xy 平面に対し、レーザを照射しながら線分を印字する印字機があります。
印字開始時、レーザ照射位置は座標 (0, 0) にある。
印字するときはレーザ照射位置を線分の端点のうちどちらか 1 つに移動させる。
その後、レーザ照射位置のある端点からもう一方の端点まで、レーザを照射しながらレーザ照射位置を一直線に移動させる。
どちらの端点から描画を始めてもよいが、線分の途中で印字を中止することはできない。
レーザ照射位置は、レーザを照射していない時は 1 秒あたり速度 S で任意の方向に、照射している時は 1 秒あたり速度 T で印字中の線分に沿って移動できる。

N 本の線分を印字したい。線分は座標 (A[i], B[i]) と座標 (C[i], D[i]) を結ぶ。
すべての線分を印字完了するまでにかかる最小の時間は何秒?

どの順番で印字するかは順列をつかって全探索すればよいです。ふたつの端点のうちどちらから印字するかはbit全探索を使えばよいです。全体の計算量は O(N! × 2^N)です。

D – joisino’s travel

D – joisino’s travel

問題の概要

N 個の町があり、M 本の双方向に移動可能な道で結ばれている。
i 本目の道は町 A[i] と町 B[i] の間を距離 C[i] で結んでいる。
R 個の町 r[0], r[1], …, r[R – 1] をすべて訪問したい。
訪問する順番を自由に選べるとして、移動距離を最小にしようとした場合の移動距離は?

まずダイクストラ法で任意の 2 つの町のあいだの移動時間を求めておきます。そのあと r の順列をすべて調べてどの順番で訪問すると移動時間が最小になるかを調べます。

D – Make 2-Regular Graph

D – Make 2-Regular Graph

N 頂点 M 辺の単純無向グラフ G があります。
G に無向辺を 1 つ追加または 1 つ削除するという操作を繰り返すことで、G をすべての頂点の次数が 2 である単純無向グラフにするために行う操作回数として考えられる最小値を求めよ。

求めようとしているグラフが連結しているのであれば頂点の順列を生成してそれぞれについて調べればよいのですが、「すべての頂点の次数が 2 である単純無向グラフ」が連結している保証はどこにもありません。

ただし、N の最大値は 8 です。N が 5 以下のときは「すべての頂点の次数が 2 である単純無向グラフ」が連結したグラフにしかなりません。N が 6 または 7 のときは連結グラフか、3 つの頂点が連結した部分グラフと残りの頂点が連結した部分グラフ = 連結成分数 2 のグラフになります。N が 8 のときはさらに加えて、4 つの頂点が連結した部分グラフが 2 つできます。問題の制約のもとでは連結成分数は 1 か 2 にしかなりません。

あとはそれらをすべて調べるだけです。

E – Modulo MST

E – Modulo MST

N 頂点 M 辺の重み付き単純連結無向グラフと正整数 K が与えられる。
辺 i は頂点 u[i] と頂点 v[i] を結んでおり、重みは w[i] である。
このグラフの全域木 T に対して、T のコストを T に含まれる辺の重みの総和を K で割ったあまりと定義する。
このグラフの全域木のコストの最小値は?

頂点数の最大値は 8 であり、このグラフが完全グラフの場合、辺の数は 28 となります。順列を使った処理をするにしては数が大きそうなのですが、bool変数の順列であれば長さ 28 でも高速に動作します。

全域木なので M 本ある辺のなかからどれを使うかを N – 1 本選び、それらの辺を使って本当に全域木が構築されるのかを調べます。構築できる場合は辺の重みの総和を計算して全域木 T のコストを計算します。あとは、そのなかから最小値を取得して出力するだけです。

D – Swapping Puzzle

D – Swapping Puzzle

問題の概要

H 行 W 列の 2 つのグリッド A, B が与えられます。
グリッド A の 隣り合う 2 つの行または列を入れ替える操作を繰り返すことで、グリッド A をグリッド B に一致させることが可能かどうかを判定せよ。
可能な場合は必要な操作回数の最小値も出力せよ。

これも順列を使って行と列を入れ替えるすべてのパターンを試してグリッド A をグリッド B に一致させることができるか試してみればよいです。

一致させることができた場合は、隣り合う 2 つの行または列を入れ替える操作を何回すればよいかを出力しなければなりません。隣り合ったものと入れ替える回数は「転倒数」を求めることでわかります。

転倒数の計算方法ですが、数列中の「左にある要素が右の要素より大きい」組の個数を数えればできます。Binary Indexed Tree を用いて高速に求める方法もありますが、行数も列数も最大 5 なので愚直な方法で実装しています。

C – False Hope

C – False Hope

3 × 3 のマス目に 1 から 9 までの数字が書き込まれており、上から i 行目、左から j 列目 に書き込まれている数字は c[i, j] です。
高橋くんは、それぞれのマスに書かれている数字をランダムな順番で知るが、縦・横・斜めの列のうちの 1 つでも以下の条件を満たしたときがっかりする。
はじめに知ったほうの 2 マスに書かれた数字が同じであり、最後に知ったマスに書かれた数字がそれと異なる。
高橋くんががっかりせずにすべてのマスに書かれた数字を知る確率は?

全部で 9 マスあるが、どのような順番で数字を知るかを全パターン試してみればよいです。

縦・横・斜めの列は全部で 8 個あるが、それらのなかで最初に知ったもの、2番めに知ったもの、最後に知ったものをリストに格納し、最後にがっかりする条件を満たしていないか確認します。

がっかりしない回数 / 9! が出力すべき解となります。

D – Send More Money

D – Send More Money

問題の概要

英小文字からなる文字列 S1, S2, S3 が与えられる。
覆面算 S1 + S2 = S3 を解け。
ただし同じ文字には同じ数字が入る。
異なる文字に同じ数字は入らない。
文字列 S1, S2, S3 の先頭の文字は 0 ではない。

文字列 S1, S2, S3 に使われている文字が 11 種類以上なら解は存在しません。10種類以下の場合はこれに 0 ~ 9 を割り当てるパターンをすべて試します。入力に使われている文字が ‘a’から順番といった規則性がないのでさいしょにそれぞれの文字に 0 ~ 9 のインデックスを割り当てます。

あとは順列全探索をして解をみつけたらそれを出力して見つからない場合は”UNSOLVABLE”を出力します。