LL Quiz 解答及び解説


問題および回答ページはこちら

下記はネタバレになります。

 

 

 

 

 

全体解説

今回の問題は、どちらも探索問題と呼ばれる問題になります。探索問題は問題ごとに様々な解き方があるため、一般的に論じることは難しいのですが、今回はその中でも深さ優先探索 (Depth First Search, DFS)で解答可能な問題を作成しました。LL Diverらしく、深く潜りながら探索していただきたいとの思いでした(もちろん、他の解法もあります)。
なおこのページでは、簡易的に C/C++/Java に似た記法を使って解答例を記述しています。

問題1

概要解説

巡回セールスマン問題 (Travelling Salesperson Problem, TSP) と呼ばれる問題です。この問題はチューリング困難問題(「多項式時間での解法はほぼ無い」と思われている問題) であり、全探索が有効に働く問題です。
ここでは2種類の解法を紹介しますが、どちらの場合も10! (10の階乗, 3628800)程度の計算量がかかります。一般的に、10!程度であれば大抵の言語・大抵のパソコンで1秒以内で答えを得ることが出来ます。

解法1

単純に、深さ優先探索を行います。
深さ優先探索の場合、アルゴリズムは大体次のようになります。

int calculateMin(
    Node currentNode, // 現在の都市
    Set notVisitedNode, // まだ訪れてない都市
    int pathLength) { // スタート地点から
                      // 現在の都市までの距離
  int minLength = MAX_INTEGER;
  for (Node nextNode : notVisitedNode) {
     // 次にnextNodeを選択したと仮定して、
     // その際の最短距離を求める
    int length = calculateMin(
        nextNode,
        // すでにnextNodeには付いているので、
        // notVisitedNodeからは取り除いて
        // もう一度訪れないようにする
        notVisitedNode - nextNode,
        // スタート地点から
        // nextNodeまでの距離を計算する。
        pathLength + distance(currentNode, nextNode));
    if (length < minLength) {
      minLength = length;
    }
  }
  return minLength;
}

calculateMin(0, {1,2,3,4,5,6,7,8,9}, 0);

解法2

permutationを使える言語の場合、もう少し簡単に書くことができます。

// 最初は順番に訪れる
int[] order = [1,2,3,4,5,6,7,8,9];
int minLength = MAX_INTEGER;
do {
  // 一番最初は必ず0から始める
  int[] fullOrder = [0] + order;
  int length = 0;
  for (int i = 0; i < fullOrder.size() - 2; i++) {
    length += distance(fullOrder[i], fullOrder[i+1];
  }
  if (length < minLength) {
    minLength = length;
  }
} while (hasNext(initialOrder))

答え

答えは、PALINDROME (回文)となります。
なお、その場合の距離は1991となりますが、これは回文数 (数字を後ろから見ても同じ値)であり、さらにこれを16進数表記にした場合も7C7 (0x7C7 == 1991)という回文数となります。

問題2

概要解説

探索問題として有名なn-Queen問題を少し変え、Queenの代わりにKingを配置する問題としました。n-Queen問題の場合は回転を同一視するなどの難しさがありますが、この問題ではもっとシンプルにしています。

解法1

全探索解法です。

int counter = 0;
int count(
  // すでに置かれた場所
  Set kings,
  // 置こうとする場所
  Cell putCell) {
  // さらにその次のcellを計算する
  nextCell = calculateNext(putCell);
  // putCellに置けるかどうかチェック
  if (canPut(kings, putCell)) {
    // putCellにおける場合は、とりあえず置いた場合を計算
    if (nextCell == null) {
      counter++;
    } else {
      count(kings + putCell, nextCell);
    }
  }
  // putCellに置かない場合も計算
  if (nextCell == null) {
    counter++;
  } else {
    count(kings, nextCell);
  }
}
count(kings, Cell(0, 0));

解法2

より計算量を減らす方法です。n-Queen問題と異なり、n-Kingの場合にはm行目のおき方はm+2行目に全く影響を与えないため、動的計画法を使うことが出来ます。

(擬似コードについては後日追加します)

答え

答えは9720になります。