2

最近、私は nim game と grundy number について学びました。アイデアを教えてください

問題: A と B が石の山でゲームをします。A がゲームを開始し、交互に動きます。それぞれの動きで、プレイヤーは山から少なくとも 1 つ以上の数の石を取り除かなければなりません。たとえば、山に 10 個の石が含まれている場合、プレイヤーは山から 1、2、3 個の石を取ることができます。AもBも完璧に弾けます。有効な動きをすることができないプレーヤーが負けます。石の数が与えられたので、両方が最適にプレーした場合に勝つプレーヤーを見つける必要があります。例

n=3 勝利、

n=5 B勝

n<=10^12

グランディ数https://www.topcoder.com/community/data-science/data-science-tutorials/algorithm-games/を使用して、少数の石でこの問題を解決できますか?

grundy 関数は g(x) で、x は残りの石です。コール F(s) は、x ストーンから取得できる残りのストーンの数のセットです。s が終端位置の場合、g(s)=0

s が終端位置でない場合、X = {g(t)| とする。t in F(s)}。そして、s の grundy 数は、X に含まれない 0 以上の最小の整数です。

例えば ​​x=10 なので F(x)={9,8,7} 1,2 または 3 個の石を取ります。(平方(10)<=3)

g(n)>0 の場合 => 最初のプレイヤーの勝利

g(0)=0

g(1)=1

g(2)=0

g(3)=1

g(4)=2 ....

しかし、このアルゴリズムは遅いです。

前もって感謝します。

4

5 に答える 5

3

私の最初の答えは最適化なしの背景理論を提供するため、2番目の答えを追加しています。しかし、OP は明らかに何らかの最適化と、多くの再帰を伴わない非常に高速なソリューションを探しているため、私は独自のアドバイスを取りました。

もちろん、これを行うための本当に手っ取り早い方法は、もう少し計算を行い、勝者か敗者かを判断できる簡単な n のプロパティを調べることです。

そこで定義した用語を使用するので、これが意味をなさない場合は、その回答を読んでください! 具体的には、nは山のサイズ、kは削除する石の数、nはサイズnの山から始まるプレイヤー A の勝利戦略があれば勝者であり、そうでない場合は敗者です。次の重要な洞察から始めましょう。

ほとんどの数字が勝者です。

なぜこれが真実なのですか?0 は敗者、1 は勝者、2 は敗者、3 は勝者、4 も同様ですが、5 もまた敗者です。しかし、数が多いほど、それはより明白になります。

ある整数pが大きく、敗者である場合、p+1、p+2、... p+k_mはすべて、sqrt(p) のサイズに近い k_mの勝者です。これは、敗者を見つけたら、それよりも大きすぎない山であれば、いくつかの石を取り除いて相手に敗者を残すことができるからです. kは最終的なパイル サイズpではなく開始パイル サイズnに関して定義されるため、重要なのはkの最大の有効な値を決定することです。

したがって、問題は、整数pが与えられた場合、 kの値が真である場合、k <= sqrt(n)ここでn = p+kになります。言い換えれば、pが与えられた場合、どのような開始パイル サイズnを使用すると、 kを削除して対戦相手にpを残すことができるかということです。n = p+kであり、値はすべて非負であるため、

k <= sqrt(n) = sqrt(p+k) ==> k^2 <= p + k ==> k^2 - k - p <= 0.

これは、任意の固定値pに対するkの二次方程式です。ソリューション セットのエンドポイントは、次の 2 次式を使用して見つけることができます。

k = (1 +- sqrt(1 + 4p))/2

したがって、不等式は(1-sqrt(1+4p))/2 と (1+sqrt(1+4p))/2 の間のkの値に対して満たされます。もちろん、sqrt(1+4p) は少なくとも sqrt(5) > 2 なので、左端点は負になります。したがって、k_m = floor((1+sqrt(1+4p))/2) .

さらに重要なことに、 pの次の敗者は数L = p + k_m + 1であると主張します。これを証明してみましょう:

定理: pが敗者の場合、L = p + k_m + 1も敗者であり、すべての整数p < n < Lが勝者です。

証明:区間[p+1, p+k_m]内のすべての整数nが勝者であることは既に示したので、 Lが敗者であることを証明するだけで済みます。

逆に、Lが勝者であるとします。次に、 L - kが (定義により) 敗者になるような1 <= k <= sqrt(L)が存在します。整数p+1、...、p+k_mが勝者であることを証明したので、 Lより小さくpより大きい数は敗者になり得ないため、 L - k <= pでなければなりません。しかし、これはL <= p + kであることを意味するので、kは式k <= sqrt(L) <= sqrt(p + k)を満たします。方程式k <= sqrt(p + k)の解は(1+sqrt(1+4p))/2よりも大きくないことを既に示したので、整数解は次の条件を満たす必要があります。k <= k_m . しかし、その後L - k = p + k_m + 1 - k >= p + k_m + 1 - k_m = p + 1 . p < L - k < Lであるため、これは矛盾であり、 pより大きくLより小さい敗者は存在しないことが既に証明されています。

QED

上記の定理は、勝者が 1 つの敗者で区切られた整数の区間に分類されること、および 2 人の敗者間の区間を計算する方法を知っているため、優れたアプローチを提供します。特に、pが敗者である場合、p + k_m + 1は敗者です。

k_m = フロア((1+sqrt(1+4p))/2) .

これで、高速で一定のスペースを必要とする純粋に反復的な方法で関数を書き直すことができます。このアプローチは、n (この場合は敗者) を見つけるか、nが 2 人の敗者の間の間隔にあると判断するまで、一連の敗者を計算するだけです。

bool is_winner(int n) {
  int p = 0;
  // loop through losers until we find one at least as large as n
  while (p < n) {
    int km = floor((1+sqrt(1+4p))/2);
    p = p + km + 1;
  }

  /* if we skipped n while computing losers, 
   * it is a winner that lies in the interval 
   * between two losers. So n is a winner as 
   * long as it isn't equal to p.*/
  return (p != n);  
}
于 2015-09-13T18:36:40.370 に答える
2

すでにかなり近いので、cmasterの回答に基づいて構築します。問題は、値を効率的に計算する方法です。

答えは: 配列全体は必要ありません。false興味深いのは値だけです。分析しましょう:

配列に値がある場合false、次のいくつかのエントリはtrue、他のプレイヤーが値に着地するように石を削除できるためfalseです。問題は、何件のtrueエントリがあるかです。

falseエントリにいる場合z、エントリxtrueエントリ ifになりx - sqrt(x) <= zます。これを解いて以下を得ることができxます:

x <= 1/2 * (1 + 2 * z + sqrt(1 + 4 * z))

これが最後のtrueエントリです。たとえばz = 2、これは を返します4。次のエントリは、対戦相手がエントリで出てくるように、プレーヤーが石を削除することしかできないため、偽になりますtrue

これを知っていれば、私たちのアルゴリズムはほぼ完成しています。既知のfalse値 (0 など) から開始します。次に、 にfalse到達するまで次の値に繰り返し移動しますn

bool isWinner(long long n)
{
    double loser = 0;
    while(n > loser)
        loser =  floor(0.5 * (1 + 2 * loser + sqrt(1 + 4 * loser))) + 1;
    return n != loser;
}
于 2015-09-13T17:58:51.957 に答える
2

このゲームは最後から再帰的に考える必要があります。明らかに、勝つには最後の石を取らなければなりません。

  • 1石:最初のプレーヤーが勝ちます。Aさんが唯一の石を取る番です。

  • 2 石: 2 番目のプレイヤーが勝ちます。A は 2 つの石を取ることはできませんが、1 つを取る必要があります。したがって、A は 1 つの石を取り、もう 1 つの石は B に任せなければなりません。

  • 3石:最初のプレーヤーが勝ちます。まだ選択の余地はありません。A は 1 つの石を取る必要があり、B が 2 つの石では勝てないことを知っているため、笑顔になります。

  • 4石:最初のプレーヤーが勝ちます。ここで、A は石を 2 つまたは 3 つ残すかを選択できます。以上のことから、A は石を 3 つ与えれば B が勝ち、石を 2 つ与えれば B が負けることを知っているので、A は石を 2 つ取ります。

  • 5 石: 2 番目のプレイヤーが勝ちます。A には 3 つまたは 4 つの石を残す選択肢がありますが、どちらかの量を与えると B が勝ちます。

nご覧のとおり、石を使ったゲームの結果を完全に把握することで、誰が石を1使ったゲームに勝つかを簡単に計算できますn-1

したがって、アルゴリズム ソリューションはブール配列 を作成しますwins。ここで、石をwins[i]与えられたプレイヤーiがゲームに勝つ場合は true です。wins[0]に初期化されfalseます。配列の残りの部分は、配列の到達可能な部分をスキャンして偽のエントリがないかどうかを最初から繰り返し埋められます。偽のエントリが見つかった場合、現在のエントリは true に設定されます。これは、A が B のためにボードを緩めた状態で残すことができるためです。それ以外の場合は false に設定されます。

于 2015-09-13T17:25:38.803 に答える
0

このようなゲーム (ハノイの塔はもう 1 つの典型的な例です) は、帰納法と再帰の数学的原理を説明するためのものであり、再帰はプログラミングに特に関連しています。

n 個の石の山が勝ちの山か負けた山かを判断したいと思います。直観的に言えば、勝利のパイルとは、対戦相手がどのような順序で選択を行ったとしても、勝利を保証するために常に一定数の石を獲得できるパイルのことです。同様に、負け山とは、あなたがどのような選択をしようとも、常に対戦相手に勝利戦略を残すものです。

明らかに、n = 0は負け組です。あなたはすでに負けています。そして、n = 1は、石を 1 つ取り、相手にn=0を残すため、勝ちの山です。n=2はどうですか?さて、あなたは石を 1 つしか取ることができず、その時点で対戦相手に勝ち山 ( n=1 ) を与えているので、n=2は負け数です。これを数学的により正確にすることができます。

定義:整数nは、n=0の場合、または1 とsqrt(n)の間のすべての整数kに対して敗者であり、nk勝者です。1 とsqrt(n)の間に整数kが存在し、nk敗者である場合、整数n勝者です。

この定義では、nは山のサイズ、kは選択した石の数です。取り除かなければならない有効な数の石が対戦相手に勝利の山を与える場合、その山は負けの山であり、勝利の山とは、何らかの選択で対戦相手に負けの山を与えるものです。

もちろん、その定義は少し不安になるはずです。なぜなら、すでに確認したn=0,1,2以外に意味があるかどうかは実際にはわからないからです。おそらく、勝者と敗者の両方の定義に当てはまる数字、またはどちらの定義にも当てはまらない数字があるでしょう。これは確かに混乱するでしょう。ここで誘導の出番です。

定理:すべての非負の整数は勝者または敗者のいずれかですが、両方ではありません。

証明:強力または完全誘導の原則を使用します。n=0は (定義により) 敗者であることがわかっており、 n=1が勝者で、n=2が敗者であることは既に示しました。これらは私たちの基本的なケースです。

ここで、整数n_0 > 2を考えてみましょう。(強い帰納法を使用して) n_0未満のすべての非負の整数は勝者または敗者のいずれかであると仮定しますが、両方ではありません。s = floor(sqrt(n_0))とし、整数のセットP = {n_0-s, n_0-s+1, ..., n_0 - 1} を考えます。( {1, 2, ..., s}は取り除く石の可能な選択肢のセットであるため、Pは対戦相手に残すことができる山のセットです。) Pの各値は非負であるため、強い帰納法によって、 n_0未満の整数、それぞれが勝者か敗者のどちらかです (両方ではない)。Pのいずれかの値が敗者である場合、定義により、n_0が勝者です (相手が負けた山を残すのに十分な数の石を取り除いたため)。そうでない場合、 Pのすべての値が勝者であるため、n_0は敗者です (何個の石を取っても、対戦相手には勝者の山が残っているため)。したがって、n_0は勝者か敗者のどちらかですが、両方ではありません。

強い帰納法により、すべての非負の整数は勝者または敗者のいずれかであり、両方ではないという結論に達します。

QED

わかりました、あなたが誘導に慣れているなら、それはかなり簡単でした. しかし、私たちが示したのは、私たちの非常に直感的な定義が実際に理にかなっており、得られるすべての山は勝者 (正しくプレイした場合) または敗者 (対戦相手が正しくプレイした場合) のいずれかであるということだけです。どちらがどれであるかをどのように判断しますか?

さて、誘導は再帰につながります。2 つの定義の再帰関数を書きましょう: nは勝者か敗者か? エラー チェックを行わない C ライクな擬似コードを次に示します。

bool is_winner(int n) {
  // check all valid numbers of stones to remove (k)
  for (int k = 1; k <= sqrt(n); k++) {
    if (is_loser(n-k)) {
      // I can force the loser n-k on my opponent, so n is a winner
      return true;
    }
  }
  // I didn't find a way to force a loser on my opponent, so this must be a loser.
  return false;
}

bool is_loser(int n) {
  if (n == 0) {  // this is our base case
    return true;
  }
  for (int k = 1; k <= sqrt(n); k++) {
    if (!is_winner(n-k)) {
      // we found a way to give them a pile that ISN'T a winner, so this isn't a loser
      return false;
    }
  }
  // Nope: every pile we can give our opponent is a winner, so this pile is a loser
  return true;
}

もちろん、すべての数字が勝者か敗者のどちらかであることは既に示したので、上記のコードはやや冗長です。is_loserしたがって、ただ返すだけ、!is_winnerまたはその逆として実装する方が理にかなっています。おそらくis_winner、スタンドアロンの実装として行うだけです。

bool is_winner(int n) {
  if (n < 0) {
    // raise error
  } else if (n == 0) {
    return false; // 0 is a loser
  } else {
    for (int k = 1; k <= sqrt(n); k++) {
      if (!is_winner(n-k)) {
        // we can give opponent a loser, this is a winner
        return true;
      }
    }
    // all choices give our opponent a winner, this is a loser
    return false;
  }
}

この関数を使用して質問に答えるには、ゲームがn 個のストーンで始まり、プレイヤー A が最初にプレイし、両方のプレイヤーが最適なプレイをした場合、プレイヤー A が の場合に勝利is_winner(n)し、プレイヤー B が の場合に勝利します!is_winner(n)。彼らのプレイがどうあるべきかを理解するために、勝っているパイルがある場合、 nkが敗者になるような有効なkを選択する必要があります(どれでも問題ありませんが、最大の値がゲームを最速で終了させます)。負け山が与えられます。何を選んでも問題ありません。これが敗者のポイントですが、繰り返しますが、kの最大値を選択すると、ゲームが早く終了します。

これは実際にはパフォーマンスを考慮していません。nは非常に大きくなる可能性があるため、考慮できることがいくつかあります。たとえば、考慮しようとしているnの一般的な小さな値を事前に計算するか、少なくとも単一の再帰呼び出し内でMemoizationを使用します。さらに、前に提案したように、kの最大値を削除すると、ゲームはより少ないターンで終了します。同様に、ループを逆にしてkの最大許容値を最初に確認すると、再帰呼び出しの数を減らすことができるはずです。

もちろん、これを行うための本当に手っ取り早い方法は、もう少し計算をして、勝者か敗者かを決定するnの簡単なプロパティを確認することです。

于 2015-09-13T17:26:01.870 に答える