【问题标题】:Tower Breakers - nim game variation with divisorsTower Breakers - 带有除数的 nim 游戏变体
【发布时间】:2017-05-29 07:58:38
【问题描述】:

我遇到了一款名为 Tower Breakers 的游戏,它似乎是 nim game 的变体。

  • 有两个玩家,玩家 1 和玩家 2。
  • 最初有n 塔,其中每个塔的高度为mnm 均为正整数。
  • 玩家 1 开始,然后他们轮流移动。
  • 在每个回合中,玩家可以选择高度为x > 1 的塔并将其高度降低为正整数y,其中1 <= y < xy 均分x
  • 如果当前玩家无法移动,则输掉游戏。

有什么制胜策略吗?如果塔在开始时的高度不相等怎么办?

【问题讨论】:

  • 我将此作为一个自我回答的问题重新发布,因为the original one 已被机器人删除。我不希望我的答案丢失。
  • 我投票结束这个问题,因为它与编程无关。
  • @Kara 好吧,这是关于算法的,它们是明确的on topic

标签: algorithm math game-theory nim-game


【解决方案1】:

一般情况

这确实可以像nim game 一样解决,但使用不同的 nimber 定义。

在最初的游戏中,塔的长度被简单地定义为它的高度。

在这个变体中,高 h(T) = x = p₁ˢ¹ … pₖˢᵏpᵢ 素数的塔的臂数为 T

n(T) = s₁ + … + sₖ

nL = (T₁, …, Tₙ) 列表的数目是通常的

n(L) = n(T₁) ⊕ … ⊕ n(Tₙ)

如果你的回合开始时nimber为0,那么无论你做什么,你都会输掉,假设其他玩家玩得很好。

否则,你只需要做一个动作,使nimber变为零。

您可以这样做,因为如果n(L) > 0 有一个塔,我们假设它是Tₙ,这样n(Tₙ) ⊕ n(L) < n(L)

存在这样的塔:n(L) 的最左边位是 1,这是因为某些塔 Tₙ 在该位位置有 1。执行n(Tₙ) ⊕ n(L) 你会杀死那个 1 并且不要改变n(Tₙ) 的更重要的位,因为它是n(L) 的最左边的位。所以n(Tₙ) ⊕ n(L) 更小。

那么你只需要将Tₙ修改为Tₙ',这样n(Tₙ') = n(Tₙ) ⊕ n(L)就可以了

n(L') = n(T₁) ⊕ … ⊕ n(Tₙ') = n(T₁) ⊕ … ⊕ n(Tₙ) ⊕ n(L) = n(L) ⊕ n(L) = 0

此移动始终是可能的,例如将高度降低到x' = p₁ˢ¹ … pₗ₋₁ˢˡ⁻¹ pₗᵈ,使用l <= k0 < d <= sₗ,这样s₁ + … + sₗ₋₁ + d = n(Tₙ) ⊕ n(L)x' 按构造划分 xx' < x,所以它们是不同的。

一旦nimber 为零,无论其他玩家做什么动作,都会影响该塔的nimber,然后xor n(L) 的总和将不再为零。然后你重复同样的策略。

特殊情况

在所有塔的高度相同的特殊情况下,

  • 如果塔数为偶数或塔的高度为 1,则总数为零,因此如果发挥最佳,玩家 2 将获胜。
  • 否则,总数将非零,因此如果发挥最佳,玩家 1 将获胜。

这实际上可以在不使用 nimbers 的情况下显示:

  • 如果塔的数量是偶数,玩家 2 可以将它们成对分组。他想要保持的不变性是每对中的两座塔都具有相同的高度。每当玩家 1 移动时,这个变体就会中断。然后玩家 2 只需要对对中的另一个塔执行相同的移动(这将是一个有效的移动,否则玩家 1 不可能这样做)。最终所有的塔都会达到高度 1,玩家 2 将获胜。

  • 如果有奇数个塔(高度 > 1),玩家 1 可以将最后一个塔的高度降低到 1。实际上,这就像消除那个塔一样。所以现在轮到玩家 2,但可玩塔的数量将是偶数。所以前面的情况适用,玩家 1 会赢。

播放

你可以在以下sn-p中玩:

function nimber(num) {
  var n = 0;
  for (var i=2; i<=num; ++i)
    while(num % i == 0) {
      ++n;
      num /= i;
    }
  return n;
}
function change(tower, newHeight, oldHeight, txt) {
  tower.textContent = newHeight;
  totalNimber ^= tower.dataset.nimber;
  tower.dataset.nimber = nimber(newHeight);
  totalNimber ^= tower.dataset.nimber;
  log.textContent += "    " + txt + " the height of the " + tower.getAttribute('data-index')
                     + "-th tower from " + oldHeight + " to " + newHeight + ".\n";
  if (newHeight == 1) tower.removeEventListener('click', playerMove);
}
function end(txt) {
  log.textContent += "    " + txt;
  playerMove = Function.prototype;
}
function prePlayerMove() {
  log.textContent += "Your turn. nimber = " + totalNimber + ".\n";
  for (var i=0; i<n; ++i) {
    var height = +towers.children[i].textContent;
    if (height != 1) return;
  }
  end("You lose");
}
function playerMove() {
  var oldHeight = +this.textContent, newHeight;
  while(!newHeight || newHeight <= 0 || oldHeight%newHeight || newHeight == oldHeight)
    newHeight = +prompt("Current tower height is " + oldHeight + ". Enter new height");
  change(this, newHeight, oldHeight, "You reduce");
  pcMove();
}
function pcMove() {
  log.textContent += "PC's turn (nimber = " + totalNimber + ").\n";
  if (totalNimber == 0) { // Oh shit.
    for (var i=0; i<n; ++i) {
      var oldHeight = +towers.children[i].textContent;
      if (oldHeight != 1)
        for (var j=2; j<=oldHeight; ++j)
          if (oldHeight % j == 0) {
            var newHeight = oldHeight / j;
            change(towers.children[i], newHeight, oldHeight, "PC reduces");
            prePlayerMove();
            return;
          }
    }
    end("PC loses");
  } else {
    for (var i=0; i<n; ++i) {
      var tower = towers.children[i];
      var objective = tower.dataset.nimber ^ totalNimber;
      if (objective < tower.dataset.nimber) {
        var oldHeight = +tower.textContent;
        var newHeight = oldHeight;
        var nim = 0;
        for (var j=2; j<=newHeight && nim<objective; ++j) {
          while(newHeight % j == 0 && nim<objective) {
            ++nim;
            newHeight /= j;
          }
        }
        newHeight = oldHeight / newHeight;
        if (nimber(newHeight) != objective) throw Error('Fatal error');
        change(tower, newHeight, oldHeight, "PC reduces");
        break;
      }
    }
    prePlayerMove();
  }
}
var n, m;
while(!Number.isInteger(n) || n < 0) n = +prompt("Number of towers");
while(!Number.isInteger(m) || m < 0) m = +prompt("Height of each tower");
var totalNimber = 0;
var towers = document.getElementById('towers');
var log = document.getElementById('log');
for (var i=0; i<n; ++i) {
  var nim = nimber(m);
  totalNimber ^= nim;
  var tower = document.createElement('div');
  tower.dataset.index = i+1;
  tower.dataset.nimber = nim;
  tower.textContent = m;
  tower.addEventListener('click', playerMove);
  towers.appendChild(tower);
}
pcMove();
#towers > div {
  display: inline-block;
  border: 1px solid;
  cursor: pointer;
  margin: 5px;
  padding: 5px;
}
<div id="towers"></div>
<pre id="log"></pre>

【讨论】:

    【解决方案2】:

    这是 Tower Breakers(等高)的答案。

    回答

    • 如果 M 为 1,则第二个玩家获胜。
    • 如果 N 是偶数 (N % 2 == 0) 且 M 不是 1,则第二个玩家获胜。
    • 如果 N 为奇数 (N % 2 == 1) 且 M 不为 1,则第一个玩家获胜。

    这是我的 C++ 代码。 (它被接受了)

    #include <bits/stdc++.h>
    using namespace std;
    int Q, N, M;
    int main() {
        cin >> Q;
        while(Q--) {
            cin >> N >> M;
            if(M == 1 || N % 2 == 0) cout << 2 << endl;
            else cout << 1 << endl;
        }
    }
    

    为什么是正确的?

    • 如果 M 为 1,很明显第二名玩家将获胜,因为第一名玩家无法移动。
    • 如果 N 是偶数,第二个玩家可以对第一个玩家执行相同的操作。例如,如果第一个玩家移动{4, 4, 4, 4} -&gt; {2, 4, 4, 4},第二个玩家可以移动{2, 4, 4, 4} -&gt; {2, 2, 4, 4}。最终第二名玩家可以赚到{1, 1, 1, ..., 1},所以第二名玩家可以获胜。
    • 如果 N 是奇数,第一个玩家可以将第一个塔的高度降低到 1,因此您可以返回“N 是偶数”的情况。因此,您可以证明第一个玩家获胜。
    • 所以,答案是正确的。

    【讨论】:

      【解决方案3】:
      1. 如果M 为 1,则两个玩家都无法移动,因此第二个玩家获胜。
      2. 如果N为偶数(N % 2 == 0)且M不为1,则第二玩家的策略是复制第一玩家的动作,因此当所有塔高为1时第二玩家获胜。
      3. 如果N 是奇数(N % 2 == 1)并且M 不是1:
      • 第一个玩家将塔的高度降低到1。现在塔的数量是(N-1),第二个 玩家成为第一个玩家,反之亦然。
      • 根据案例 2,获胜者是第一个玩家。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多