一般情况
这确实可以像nim game 一样解决,但使用不同的 nimber 定义。
在最初的游戏中,塔的长度被简单地定义为它的高度。
在这个变体中,高 h(T) = x = p₁ˢ¹ … pₖˢᵏ 和 pᵢ 素数的塔的臂数为 T
n(T) = s₁ + … + sₖ
n 塔L = (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 <= k 和0 < d <= sₗ,这样s₁ + … + sₗ₋₁ + d = n(Tₙ) ⊕ n(L)。 x' 按构造划分 x 和 x' < 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>