记得大二的时候还研究过,后来一直放着也没弄过。 不过今天做TC的时候自己分析到了博弈,发现怎么都不记得了。复习一下.......... 以后工作可能要与Java 打交道了,所以 ......还是练练Java实现吧....
寻找平衡状态(也称必败态, 奇异局势),(满足:任意非平衡态经过一次操作可以变为平衡态)
(一)巴什博奕(Bash Game):
只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个.最后取光者得胜.
n = (m+1)r+s , (r为任意自然数,s≤m), 即n%(m+1) != 0, 则先取者肯定获胜
import java.util.Scanner; public class Main { /* * 谁先取到n-1谁就赢了 */ public static final double Gold = ((1+ Math.sqrt(5.0))/2.0); public static void main(String[] args) { Scanner in = new Scanner(System.in); int n, k; while (in.hasNext()) { n = in.nextInt(); k = in.nextInt(); if (n == 0 && k == 0) break; if ((n - 1) % (k + 1) != 0) System.out.println("Tang"); else System.out.println("Jiang"); } } }
(二)威佐夫博奕(Wythoff Game):
有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜.
(ak,bk)(ak ≤ bk ,k=0,1,2,...,n)表示奇异局势
求法:
ak =[k(1+√5)/2], bk= ak + k (k=0,1,2,...,n 方括号表示取整函数)
判断:
Gold=(1+sqrt(5.0))/2.0;
1)假设(a,b)为第k种奇异局势(k=0,1,2...) 那么k=b-a;
2)判断其a==(int)(k*Gold),相等则为奇异局势
(注:采用适当的方法,可以将非奇异局势变为奇异局势.
假设面对的局势是(a,b)
若 b = a,则同时从两堆中取走 a 个物体,就变为了奇异局势(0,0);
1. 如果a = ak,
1.1 b > bk, 那么,取走b - bk个物体,即变为奇异局势(ak, bk);
1.2 b < bk 则同时从两堆中拿走 ak – a[b – ak]个物体,变为奇异局势( a[b – ak] , a[b – ak]+ b - ak);
2 如果a = bk ,
2.1 b > ak ,则从第二堆中拿走多余的数量b – ak
2.2 b < ak ,则 若b = aj (j < k) 从第一堆中拿走多余的数量a– bj; (a > bj)
若b = bj (j < k) 从第一堆中拿走多余的数量a– aj; ( a > aj)
)
pku 1067
import java.util.Scanner; public class Main { public static final double Gold = ((1+ Math.sqrt(5.0))/2.0); public static void main(String[] args) { Scanner in = new Scanner(System.in); Integer a = 0,b = 0; while (in.hasNext()) { a = in.nextInt(); b = in.nextInt(); if (a > b) { int t; t = a; a = b; b = t; } int k = b - a; if ((int)(k*Gold) == a) System.out.println(0); else System.out.println(1); } } }
(三)尼姆博奕(Nimm Game):
有n堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜.
任何奇异局势(a1, a2, … , an)都有a1(+)a2(+)…(+)an =0. ( (+)为 按位与)
例题:pku 2234
import java.util.Scanner; public class Main { public static final double Gold = ((1+ Math.sqrt(5.0))/2.0); public static void main(String[] args) { Scanner in = new Scanner(System.in); int n, k; while (in.hasNext()) { n = in.nextInt(); int ans = 0; for (int i = 0; i < n; ++i) { k = in.nextInt(); ans ^= k; } if (ans == 0) System.out.println("No"); else System.out.println("Yes"); } } }
import java.util.Scanner; public class Main { /* * 首先我们考虑所有棋子都相邻的情况,这种情况是一种必败态,因为不论我回退多少个格子,对方总能跟上来,然后我肯定是必败的。 * 那么怎么判断我是否能够使对方到达这个状态的, Nim判断的是最后谁取得了最后的棋子,只要利用Nim来判断我最后是否能够走完所有空格使对方面对这个状态即可(也即取得剩下的所有棋子) * 只要对方面对了这个必败态,那么我就能够取胜 */ public static void main(String[] args) { Scanner in = new Scanner(System.in); int n,m,a,b; while (in.hasNext()) { n = in.nextInt(); m = in.nextInt(); int ans = 0; for (int i = 0; i < n; ++i){ a = in.nextInt(); b = in.nextInt(); ans ^= (Math.abs((b - a)) - 1); } if (ans != 0) System.out.println("I WIN!"); else System.out.println("BAD LUCK!"); } } }
题解:
剩余1堆石头:先取者全部拿走,胜利。
剩余2堆石头:假设两堆石头的数量相等,均为m。对于(m,m),先取者没办法一次将两堆石头拿走,则后取者总可以模仿先取者的动作,只需换一下操作对象,使两堆石头变成 (n,n)。最后肯定有某一次先取者操作后石头只剩下一堆,这时后取者将剩下的一堆全部拿走,胜利。
也就说,对于(m,m),后取者必胜。
如果两堆石头不相等,(m,n),则先取者把多的那一堆拿走一些,使两堆相等(n,n)。
也就说,对于(m,n),先取者必胜。
剩余3堆石头:(x,y,z)其中(x<=y<=z),先取者可以把z分(y-x)到x上,剩下的全部拿走,于是局面变成了(y,y,0)。也即是说对于3堆石头,先取者必胜。
对于4堆石头:如果(m,m,n,n),这样和两堆石头的(m,m)局面是类似的,先取者必败,后取者必胜。但如果不是这样的情况,(a,b,c,d)其中(a<=b<=c<=d),先取者操作最后一堆石 头,总可以使局面变成(a,c,c,a)。这样就把必败局留给了后取者,也就是说这种情况先取者必胜。
对于n堆石头,分析和前面类似。。。。
总的来说,对于n堆石头:
如果n是奇数,先取者必胜。
如果n是偶数,石头堆排序后可以写成(a,a,b,b,c,c,d,d,....)则先取者必败,否则先取者必胜
import java.util.Arrays; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = 0; int[] num = new int[101]; while (in.hasNext()) { Arrays.fill(num, 0); n = in.nextInt(); if (n == 0) break; int[] a = new int[n]; for (int i = 0; i < n; ++i){ a[i] = in.nextInt(); } if ((n&1) == 1) { System.out.println("1"); } else { for (int i = 0; i < n; ++i) { num[a[i]]++; } int ans = 0; for (int i = 0; i < n; ++i) { if ((num[a[i]] & 1) != 0) { ans = 1; break; } } System.out.println(ans); } } } }
例题:pku 1704
大意:Georigia和Bob玩棋子游戏,棋子排成一行,类似于落入x坐标上,最左端 为原点0所在位置,棋子i坐落于x轴上的chess[i](chess[i]>0)位置, 游戏规则如下: 1.每人每次可以且只能移动一个棋子 2.每次移动棋子的格子数不限 3.只能向左移动棋子 4.移动过程中不能覆盖或越过其它棋子 5.当谁没有棋子可以移动时,就输了 6.两人轮流移动石子,女士优先,Georigia总是先移动 7.最左端所在坐标为0,亦即放在1位置的棋子不能再往左移 假设两人一样聪明,给定棋子的初始位置,判断谁将是赢家。 分析: 1.将棋子按位置升序排列,从前往后将其两两组成一对。 如果棋子数为奇数,则假设0位置上放有一棋子,与原先位置最小的棋子组成 一对,剩下的依旧从前往后两两组成一对. 2.从(1)的分组中取出两组相邻的棋子,假设其位置分别为 (chess[i],chess[i+1]),(chess[i+2],chess[i+3]), 此时若我们移动chess[i+2]若干步,对手总能移动chess[i+3]相同步数 故这里chess[i+1]与chess[i+2]之间有多少空格对结局并没有影响,即 每组棋子的前一个与其前面一组的后一个棋子所在位置的距离对结局并没有影响 故本题我们只需考虑同一组棋子间的距离 3.对(2)建模,假设现已将n组棋子分为s=(n+1)/2组,每组前后两棋子间 的距离分别为dis[0],dis[1],dis[2]...dis[s-1], 那么现在问题就演变为有s堆石子,其中第i堆石子个数为dis[i](0<=i<s), 两人轮流取石子,每次可选任意一堆石子取,且每次至少取一个,多者不限,最后取光者取胜。 现在就是最裸的Nimm Game问题了,套用模型即可. import java.util.Arrays; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); int T = in.nextInt(); while ((T--) != 0) { int n = in.nextInt(); int[] a = new int[n]; int ans = 0; for (int i = 0; i < n; ++i) a[i] = in.nextInt(); Arrays.sort(a); //for (int i = 0; i < n; ++i) System.out.println(a[i]); if((n&1) == 1) { ans ^= (a[0] - 0 - 1); for (int i = 2; i < n; i += 2) { ans ^= (a[i] - a[i - 1] - 1); } } else { for (int i = 1; i < n; i += 2) { ans ^= (a[i] - a[i - 1] - 1); } } //System.out.println(ans); if (ans == 0) { System.out.println("Bob will win"); } else { System.out.println("Georgia will win"); } } } }
例题:pku 1082 (大量分析… 结论很简单。 也可以根据简单的推论模拟实现。)