以下资料来自:http://blog.csdn.net/Dinosoft/article/details/6795700 http://qianmacao.blog.163.com/blog/static/203397180201222945212647/
http://blog.csdn.net/qinmusiyan/article/details/7950642#
HDU 1850 Being a Good Boy in Spring Festival
下面是一个二人小游戏:桌子上有M堆扑克牌;每堆牌的数量分别为Ni(i=1…M);
两人轮流进行;每走一步可以任意选择一堆并取走其中的任意张牌;
桌子上的扑克全部取光,则游戏结束;最后一次取牌的人为胜者。
现在我们不想研究到底先手为胜还是为负,我只想问大家:
——“先手的人如果想赢,第一步有几种选择呢?”
1、如果a1^a2^a3^...^an =0(即:nim - sum =0),说明先手没有必胜策略,方法数肯定为0;
2、假设先手有必胜策略。
问题则转化为=>在任意一堆拿走任意K张牌,并且剩下所有堆nim - sum =0(P-position)的方案总数。
①现在我们先看一个例子(5、7、9),并假设从第一堆取任意K张牌。
排除第一堆牌的nim - sum 为7^9=14
0111
^1001
--------------
1110
如果要使所有堆的nim-sum =0成立,则第一堆取掉K张以后必定为1110,因为X^X=0.
所以要观察5- K =15(K >0)成立,此例子(在第一堆取任意K张牌)明显不成立。
但不代表在第二或第三堆取任意K张牌的解不成立。
②现在看第二例子(15、7、9),并假设从第一堆取任意K张牌。
排除第一堆的nim - sum 为7^9=14,和第一个例子相同,所以问题变为观察15- K =14(K >0)是否成立。
显然这个例子成立。
3、总结得出:
在任意一堆拿任意K张牌,并且所有堆的nim-sum =0成立的条件为:
排除取掉K张牌的那一堆的nim-sum必须少于该堆牌上的数量(例子②),
否则不能在此堆上取任意K张牌所有堆的nim-sum =0成立(例子①)
故总方案数为(在任意一堆拿任意K张牌,并且所有堆的nim-sum =0成立)的总数。
1 #include <stdio.h> 2 3 int main(){ 4 int i,n,sum,a[10001]; 5 while(EOF != scanf("%d",&n)){ 6 if(n == 0) 7 break; 8 sum = 0; 9 for(i = 0;i < n;i++){ 10 scanf("%d",&a[i]); 11 sum ^= a[i]; 12 } 13 if(sum == 0) 14 printf("0 "); 15 else{ 16 int count = 0; 17 for(i=0;i<n;i++){ 18 sum ^= a[i]; 19 if(a[i] > sum) 20 count++; 21 sum ^= a[i]; 22 } 23 printf("%d ",count); 24 } 25 } 26 return 0; 27 }
hdu-2147:kiki's game
Recently kiki has nothing to do. While she is bored, an idea appears in his mind, she just playes the checkerboard game.The size of the chesserboard is n*m.First of all, a coin is placed in the top right corner(1,m). Each time one people can move the coin into the left, the underneath or the left-underneath blank space.The person who can't make a move will lose the game. kiki plays it with ZZ.The game always starts with kiki. If both play perfectly, who will win the game? Input Input contains multiple test cases. Each line contains two integer n, m (0<n,m<=2000). The input is terminated when n=0 and m=0. Output If kiki wins the game printf "Wonderful!", else "What a pity!". Sample Input 5 3 5 4 6 6 0 0
P点:就是P个石子的时候,对方拿可以赢(自己输的)
N点:就是N个石子的时候,自己拿可以赢
现在关于P,N的求解有三个规则
(1):最终态都是P
(2):按照游戏规则,到达当前态的前态都是N的话,当前态是P
(3):按照游戏规则,到达当前态的前态至少有一个P的话,当前态是N
题意:
在一个m*n的棋盘内,从(1,m)点出发,每次可以进行的移动是:左移一,下移一,左下移一。然后kiki每次先走,判断kiki时候会赢(对方无路可走的时候)。
我们可以把PN状态的点描绘出来::
代码很简单,只需要判断奇偶
J.xiaodao 我爱你! |
|||||
|
|||||
Description |
|||||
自从见到 xiaodao 的第一眼起,我就不可救药的爱上了她。 xiaodao——如果四个顶点有石子,那么你可以把石子都拿走(不能剩下),如果没有石子你得再选一次,直到有拿走石子为止。
xiaodao——我们规定,如果谁没有石子拿了,谁就输,听明白了么! |
|||||
Input |
|||||
第一行是一个整数 T 代表以下 T 组数据。 每组数据占一行,两个整数 N , M。(注意,当 N * M 为奇数的时候中心的那一个石子已经被拿走了) |
|||||
Output |
|||||
输出一行,如果是 DS 获胜那么输出 "DS" , 否则输出 "xiaodao" . (不带引号) |
|||||
Sample Input |
|||||
2 2 2 3 3 |
|||||
Sample Output |
|||||
DS xiaodao |
|||||
Hint |
|||||
1. 以下是两个样例的游戏开始的示意图
2. 对于非法情况的界定 如图,对于下图的N=9,M=9情况,√的代表合法操作,×的代表非合法操作。 非合法操作有两种——超出边界或者格子四个顶点没有石子。
|
代码很简短,只需要判断奇偶性
1 #include<stdio.h> 2 int main(){ 3 int t,m,n; 4 scanf("%d",&t); 5 while(t--){ 6 scanf("%d%d",&m,&n); 7 if(n%2==0&&m%2==0) 8 printf("DS "); 9 else 10 printf("xiaodao "); 11 } 12 return 0; 13 }
//文绉绉的定义,随便看看就好
Nim游戏是组合游戏(Combinatorial Games)的一种,准确来说,属于“Impartial Combinatori Games”(以下简称 ICG)。满足以下条件的游戏是ICG(可能不太严谨):
1、有两名选手;
2、两名选手交替对游戏进行移动(move),每次一步,选手可以在(一般而言)有限的合法移动集合中任选一种进行移动;
3、对于游戏的任何一种可能的局面,合法的移动集合只取决于这个局面本身,不取决于轮到哪名选手操作、以前的任何操作、骰子的点数或者其它什么因素;
4、如果轮到某名选手移动,且这个局面的合法的移动集合为空(也就是说此时无法进行移动),则这名选手负。根据这个定义,很多日常的游戏并非 ICG。例如象棋就不满
足条件3,因为红方只能移动红子,黑方只能移动黑子,合法的移动集合取决于轮到哪名选手操作。
Nim 游戏
定义:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。
当然,特殊情况就是只有一堆或者两堆石子,自己去意淫一下吧。
P-position和N-position
其中P 代表Previous,N 代表Next。 P-position是必败态,N-position是必胜态。
必败(必胜)点属性
(1) 所有终结点是必败点(P点); //容易理解
(2) 从任何必胜点(N点)操作,至少有一种方法可以进入必败点(P点); //就是那种我们要走的方法
(3)无论如何操作, 从必败点(P点)都只能进入必胜点(N点). //对手走完又只能把N留给我们
取子游戏算法实现
步骤1:将所有终结位置标记为必败点(P点);
步骤2: 将所有一步操作能进入必败点(P点)的位置标记为必胜点(N点)
步骤3:如果从某个点开始的所有一步操作都只能进入必胜点(N点) ,则将该点标记为必败点(P点) ;
步骤4: 如果在步骤3未能找到新的必败(P点),则算法终止;否则,返回到步骤2
/*
上面的说法似乎不太好理解。我简单讲下。显然我们可以从终结条件递推回来。往后往前开始扫描,(终结状态方向为后)
a.如果当前是P点,那么一步(向前)可以走到的都是N点
b.如果当前点未标明P/N属性,那么看看该点向后走是不是都只能到达N点,如果是,那么该点是P点。
c.如果该点是N点,倒无法确定什么。
如果没办法标一个点,那么异常结束。
*/
例题
我是用P N状态递推出来小规模的数据后找规律的,直接递推提交爆内存。(x,y)中,x和y都是奇数的话,那么无论怎么移动,x和y中都有至少有一个偶数,而且再移动一步后,都可以变成xy全奇数。
Nim-Sum
直接说结论好了。(Bouton's Theorem)对于一个Nim游戏的局面(a1,a2,...,an),
它是P-position 当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。
根据定义,证明一种判断position的性质的方法的正确性,只需证明三个命题:
1、这个判断将所有terminal position 判为P-position;
2、根据这个判断被判为N-position 的局面一定可以移动到某个 P-position;
3、根据这个判断被判为 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'不是一个合法的移动。证毕。
// 简单理解,异或2次可以翻转回来,我们只改一个数字的话,翻不回来。
例题
Being a Good Boy in Spring Festival
求Nim-Sum,非零才能赢。然后每次从Nim-Sum去掉一堆扑克牌,结果是否小于这堆扑克牌数,如果能,方法数+1
ZOJ 3529 A Game Between Alice and Bob
上面强调了一点,游戏中每次拿走的石头数目是不限的,而这道题需要运用技巧进行转换。
求出因子数即可!可以用dp,然后在筛素数的时候顺便记录一下其中一个最大的因子。源码可以看这里:
http://growthinking.com/archives/1777
Sprague-Grundy 函数
现在我们来研究一个看上去似乎更为一般的游戏:给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移动者判负。事实上,这个游戏可以认为是所有 Impartial Combinatorial Games 的抽象模型。也就是说,任何一个 ICG都可以通过把每个局面看成一个顶点,对每个局面和它的子局面连一条有向边来抽象成这个“有向图游戏”。下面我们就在有向无环图的顶点上定义 Sprague-Garundy函数。
mex(minimal excludant)运算
定义表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于一个给定的有向无环图,定义关于图的每个顶点的 Sprague-Garundy 函数 g 如下:
g(x)=mex{ g(y) | y是 x的后继 }。
来看一下 SG 函数的性质。首先,所有的 terminal position 所对应的顶点,也就是没有出边的顶点,其 SG 值为 0,因为它的后继集合是空集。然后对于一个g(x)=0 的顶点 x,它的所后继y都满足 g(y)!=0。对于一个g(x)!=0的顶点,必定存在一个后继 y满足g(y)=0。
以上这三句话表明,顶点 x 所代表的 postion 是 P-position 当且仅当 g(x)=0(跟P-positioin/N-position 的定义的那三句话是完全对应的)。我们通过计算有向无环图的每个顶点,就可以对每种局面找到必胜策略了。
例题
sg函数小试牛刀,哈哈
总结一下
一堆石子的游戏 ---->两堆石子的游戏 ---> Nim游戏 ---> 有向图
P/N position <----> Nim-Sum <----> SG函数 //想起一个词 "同构"