Description
牛客网 2018校招真题 不想出差的HR
Solving Ideas
详情请参考 Nim游戏
Nim游戏是经典的公平组合游戏(ICG),满足以下条件的游戏是ICG:
- 有两名选手;
- 两名选手交替对游戏进行移动(move),每次移动选手可以在有限的合法移动集合中任选一种进行移动;
- 对于游戏的任何一种可能的局面(position),合法的移动集合只取决于这个局面本身,不取决于轮到哪名选手操作、以前的任何操作、骰子的点数或者其它什么因素;
- 如果轮到某名选手移动,且这个局面的合法的移动集合为空(也就是说此时无法进行移动),则这名选手负。
对于position可以分为两类:
- P-position:当前局面下,先手必败
- N-position:当前局面下,先手必胜
关于position的转移有如下性质:
- 无法进行任何移动的局面(也就是terminal position)是P-position;
- 可以移动到P-position的局面是N-position;
- 所有移动都导致N-position的局面是P-position。
关于判断position性质的方法的正确性的证明,只需证明三个命题:
- 这个判断将所有terminal position判为P-position;
- 根据这个判断被判为N-position的局面一定可以移动到某个P-position;
- 根据这个判断被判为P-position的局面无法移动到某个P-position。
第一个命题显然,terminal position只有一个,就是全0,异或仍然是0。
第二个命题,对于某个局面(a1,a2,...,an)
,若a1^a2^...^an<>0
,一定存在某个合法的移动,将ai
改变成ai'
后满足a1^a2^...^ai'^...^an=0
。不妨设a1^a2^...^an=k
,则一定存在某个ai
,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai^k<ai
一定成立。则我们可以将ai
改变成ai'=ai^k
,此时a1^a2^...^ai'^...^an=a1^a2^...^an^k=0
。
第三个命题,对于某个局面(a1,a2,...,an)
,若a1^a2^...^an=0
,一定不存在某个合法的移动,将ai
改变成ai'
后满足a1^a2^...^ai'^...^an=0
。因为异或运算满足消去率,由a1^a2^...^an=a1^a2^...^ai'^...^an
可以得到ai=ai'
。所以将ai
改变成ai'
不是一个合法的移动。证毕。
根据这个定理,我们可以在O(n)的时间内判断一个Nim的局面的性质,且如果它是N-position,也可以在O(n)的时间内找到所有的必胜策略。Nim问题就这样基本上完美的解决了。
结论:
(Bouton’s Theorem)对于一个Nim游戏的局面(a1,a2,...,an)
,它是P-position当且仅当a1^a2^...^an=0
,其中^
表示异或(xor)运算。
Time complexity :
O
(
n
)
O(n)
O(n)
Space complexity :
O
(
1
)
O(1)
O(1)
Solution
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* Nim游戏
* @author wylu
*/
public class Main {
static String[] room = {"A", "B", "C"};
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] strs = br.readLine().split(",");
int[] a = new int[strs.length];
int k = 0;
for (int i = 0; i < strs.length; i++) {
a[i] = Integer.parseInt(strs[i]);
k ^= a[i];
}
if (k == 0) {
//P-position,先手必败
System.out.println(1);
} else {
//N-position,先手必胜
for (int i = 0; i < a.length; i++) {
//寻找N-position移动到某个P-position的方法
int num = k ^ a[i];
if (a[i] - num < 0) {
continue;
}
// for (int j = 0; j < a.length; j++) {
// tmpXor ^= (j == i) ? num : a[j];
// }
int tmpXor = k ^ a[i] ^ num;
if (tmpXor == 0) {
System.out.println(room[i] + "," + (a[i] - num));
break;
}
}
}
}
}