2024/5/28 P1247 取火柴游戏

取火柴游戏

题目描述

输入 k k k 及 k k k 个整数 n 1 , n 2 , ⋯   , n k n_1,n_2,\cdots,n_k n1,n2,⋯,nk,表示有 k k k 堆火柴棒,第 i i i 堆火柴棒的根数为
n i n_i ni;接着便是你和计算机取火柴棒的对弈游戏。取的规则如下:每次可以从一堆中取走若干根火柴,也可以一堆全部取走,但不允许跨堆取,也不允许不取。

谁取走最后一根火柴为胜利者。

例如: k = 2 k=2 k=2, n 1 = n 2 = 2 n_1=n_2=2 n1=n2=2,A 代表你,P 代表计算机,若决定 A 先取:

  • A: ( 2 , 2 ) → ( 1 , 2 ) (2,2) \rightarrow (1,2) (2,2)→(1,2),即从第一堆中取一根。
  • P: ( 1 , 2 ) → ( 1 , 1 ) (1,2) \rightarrow (1,1) (1,2)→(1,1),即从第二堆中取一根。
  • A: ( 1 , 1 ) → ( 1 , 0 ) (1,1) \rightarrow (1,0) (1,1)→(1,0)。
  • P: ( 1 , 0 ) → ( 0 , 0 ) (1,0) \rightarrow (0,0) (1,0)→(0,0)。P 胜利。

如果决定 A A A 后取:

  • P: ( 2 , 2 ) → ( 2 , 0 ) (2,2) \rightarrow (2,0) (2,2)→(2,0)。
  • A: ( 2 , 0 ) → ( 0 , 0 ) (2,0) \rightarrow (0,0) (2,0)→(0,0)。A 胜利。

又如 k = 3 k=3 k=3, n 1 = 1 n_1=1 n1=1, n 2 = 2 n_2=2 n2=2, n 3 = 3 n_3=3 n3=3, A A A 决定后取:

  • P: ( 1 , 2 , 3 ) → ( 0 , 2 , 3 ) (1,2,3) \rightarrow (0,2,3) (1,2,3)→(0,2,3)。
  • A: ( 0 , 2 , 3 ) → ( 0 , 2 , 2 ) (0,2,3) \rightarrow (0,2,2) (0,2,3)→(0,2,2)。
  • A 已将游戏归结为 ( 2 , 2 ) (2,2) (2,2) 的情况,不管 P 如何取 A 都必胜。

编一个程序,在给出初始状态之后,判断是先取必胜还是先取必败,如果是先取必胜,请输出第一次该如何取。如果是先取必败,则输出 lose

输入格式

第一行,一个正整数 k k k。

第二行, k k k 个整数 n 1 , n 2 , ⋯   , n k n_1,n_2,\cdots,n_k n1,n2,⋯,nk。

输出格式

如果是先取必胜,请在第一行输出两个整数 a , b a,b a,b,表示第一次从第 b b b 堆取出 a a a

个。第二行为第一次取火柴后的状态。如果有多种答案,则输出 ⟨ b , a ⟩ \lang b,a\rang ⟨b,a⟩ 字典序最小的答案( 即 b b b 最小的前提下,使
a a a 最小)。

如果是先取必败,则输出 lose

样例 #1

样例输入 #1

3 3 6 9

样例输出 #1

4 3 3 6 5

样例 #2

样例输入 #2

4 15 22 19 10

样例输出 #2

lose

提示

数据范围及约定

对于全部数据, k ≤ 500000 k \le 500000 k≤500000, n i ≤ 1 0 9 n_i \le 10^9 ni≤109。

又是参加学校每日题的一天,熟练的点开了题目的算法标签,想看看这个题我有没有一点点涉猎(又是及时放弃的一天(bushi))一看:博弈论。瞬间想起了之前在b站刷到过却放在收藏夹吃灰,于是决定好好看看而不是直接放弃(

让我们分析分析这道题。经典的Nim游戏 P2197 【模板】Nim 游戏

首先我们需要认识到,仅有一堆棋子存在时,出现胜负决断

n=1: 先手全拿,先手必胜。

n=2:有两种情况,一种可能相同,一种情况一堆比另一堆少(多)

    (m,m) 按照"有一学一,照猫画猫"法,先手必输。

    (m,M)先手先从多的一堆中拿出(M-m)个,此时后手面对(m,m)的局面先手必胜。

术语:正经人称(m,m)的局面为奇异局势

n=3:(m,m,M)先手必胜局,先手可以先拿M,之后变成了(m,m,0)的局面,是不是很熟悉~

(a1,a2,a3)的话,举个例子(1,2,3),先手取完之后可能的局面为(0,2,3),(1,1,3),(1,0,3),(1,2,2),(1,2,1),(1,2,0)都是之前讲过的,情况如下:

这些的结果可以总结为:异或均为0

这块的异或到底是怎么蹿出来的,我觉得洛谷题解有一篇说的挺有意思

异或有一个特殊的规律,就是一堆数异或时,若在同一个二进制位上1的个数是偶数,那么这一位异或起来以后是0,否则为1

二进制的话就是可以使用0/1表示所有数字

我们来看上一个游戏,我们将这两堆的剩余的火柴数转变成二进制。

发现我们先手取走一个数,就是改变其二进制为上的1的个数(只考虑奇偶性),而后手再去取的话就是将其奇偶性再变回来

然后我们再回去看为什么异或和是0时先手必输,因为先手拿走了某些火柴时,我们可以根据其拿走火柴的二进制表示,在其他一堆中拿走一些一些数字,使得其异或和重新为0;

怎么搞呢? 我们可以拿走一些数,也就是减某一个数,使得先手拿完后,(啰嗦警告)

所有堆中的每个二进制上的一的个数的和,我们总可以通过加减一个数,达到在某一个二进制位的1的个数进行加一or减一的效果

使得某一位二进制上的1的个数变为偶数。

从而使得游戏又恢复到了一开始的局面
题解


先手必胜,即先手可以拿走一些火柴,使得后手必败,而必败态是火柴堆的异或和为零;那么我们求的,就是先手拿走一些火柴后,新的火柴堆异或和为零的方案。设原异或和为X

当(a2 xor X)<a2时,能取走火柴。

cpp 复制代码
#include <iostream>
#include<algorithm>


using namespace std;
int k, n[500100], x;

int main()
{
	cin >> k;
	for (int i = 1; i <= k; i++)
	{
		cin >> n[i];
		x ^= n[i];
	}
	if (x == 0) { cout << "lose"; return 0; }
	for (int i = 1; i <= k; i++)
	{
		if ((n[i] ^ x) < n[i])
		{
			cout << n[i] - (n[i] ^ x) << " " << i << endl;
			n[i] = n[i] ^ x;
			break;
		}
	}
	for (int i = 1; i <= k; i++)
	{
		cout << n[i] << " ";
	}
	return 0;
}

这里贴一下感觉比较推荐的有关文章
数学知识------博弈论(巴什博奕、尼姆博奕、威佐夫博奕)思路及例题
题解 P1247 【取火柴游戏】
[学习笔记] (博弈论)Nim游戏和SG函数

相关推荐
唐诺3 小时前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨4 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客4 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin4 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos6 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室6 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0017 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我587 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
fpcc7 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存
呆萌很7 小时前
C++ 集合 list 使用
c++