《编程之美-微软技术面试心得》这本书中的1.11节的转化问题研究:nim拈游戏研究

《编程之美-微软技术面试心得》这本书中的1.11节有一个问题:

一堆石块,A和B两个人从里面拿,每次只能拿一个或者任意连续的两个,最后拿到的算赢,先拿者是否有必胜策略:

这个问题较为简单:先拿者有必赢策略,只要按照如下规则拿即可,如果有奇数个石块,先拿者只拿中间的一个,如果偶数块,先拿者拿中间的两个,之后的策略就很简单了,和对手对称的拿就行了,对手怎么拿,先拿者也怎么拿,一定会拿到最后一块。

现在将问题转化一下,如果最后拿到算输的话,先拿者是否还有必胜策略呢??

如果共有1个石块,有必赢策略

2:有

3:有

4:无

5:有(拿掉边上的一个,就使得对方没有必赢策略)

6:有(拿掉边上的两个)

规则1:如果n没有必赢策略,那么n+1和n+2一定有必赢策略

给定一堆石块n个,怎样判断先拿者是否有必胜策略呢,就是判断从n个石块任意拿一个或者连续的两个,以后的结果如果没有必赢策略,那么这n个石块的堆就有了必赢策略:

用[x,y]表示一个被拆分了的堆,首先计算[x,y]是否有必赢策略。

规则2:n个石块的一段,拿掉任意一个或者连续的两个以后剩下[x,y],如果任意一种情况没有必赢策略,那么n个一堆就有必赢策略,否则,没有必赢策略

规则3:[x,y]是否有必赢策略可以通过和规则2一样的推理方法,又所有[x,y]的拆分情况是否有必赢策略决定。

规则4:初始条件相关,奇数个1的组,有必赢策略,退化为1个1的组,偶数个1,无必赢策略,退化为两个1的组。

使用以上4个规则,则可以得出一个算法:

package shikuai;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;

/**
 * 
 * 一堆石块,A和B两个人从里面拿,每次只能拿一个或者任意连续的两个,最后拿到的算输,先拿者是否有必胜策略
 * 
 * @author wan
 * 
 */

public class ShiKuaiProblem {

	static class ShiKuai implements Comparable<ShiKuai> {
		int value;
		ArrayList<ShiKuai> res = new ArrayList<ShiKuai>();

		public ShiKuai(int v) {
			this.value = v;
		}

		public ArrayList<ShiKuai> splits(int i) {
			res.clear();
			if (i == 0) {
				if (this.value != 1)
					res.add(new ShiKuai(this.value - 1));
			} else if (i < this.value - 1) {
				res.add(new ShiKuai(i));
				res.add(new ShiKuai(this.value - i - 1));
			} else
				res.add(this);
			return res;
		}

		public ArrayList<ShiKuai> splits2(int i) {
			res.clear();
			if (i == 0) {
				if (this.value > 2)
					res.add(new ShiKuai(this.value - 2));
			} else if (i < this.value - 2) {
				res.add(new ShiKuai(i));
				res.add(new ShiKuai(this.value - i - 2));
			} else
				res.add(this);
			return res;
		}

		@Override
		public int compareTo(ShiKuai o) {
			return this.value - o.value;
		}

		@Override
		public String toString() {
			return String.valueOf(this.value);
		}
	}

	static class ShiKuaiGroup {

		HashSet<ShiKuai> group;
		ArrayList<ShiKuai> res;

		public ShiKuaiGroup() {
			group = new HashSet<ShiKuai>();
			res = new ArrayList<ShiKuai>();
		}

		public ShiKuaiGroup(int... vs) {
			this();
			for (int i : vs) {
				group.add(new ShiKuai(i));
			}
		}

		@Override
		public int hashCode() {
			int h = 0;
			int i = 1;
			for (ShiKuai sk : group) {
				h += Math.pow(sk.value, i++);
			}
			return h;
		}

		@Override
		public boolean equals(Object obj) {
			ShiKuaiGroup o = (ShiKuaiGroup) obj;
			// if (group.size() != o.group.size())
			// return false;
			// if (this.group.containsAll(o.group))
			// return true;
			// return false;
			return this.toString().equals(o.toString());
		}

		@Override
		protected ShiKuaiGroup clone() {
			ShiKuaiGroup cl = new ShiKuaiGroup();
			for (ShiKuai sk : this.group) {
				cl.group.add(new ShiKuai(sk.value));
			}
			return cl;
		}

		@Override
		public String toString() {
			int[] arr = new int[this.group.size()];
			int i = 0;
			for (ShiKuai sk : this.group) {
				arr[i++] = sk.value;
			}
			Arrays.sort(arr);
			StringBuffer sb = new StringBuffer();
			sb.append("[" + arr[0]);
			for (int j = 1; j < arr.length; j++) {
				sb.append(" " + arr[j]);
			}
			sb.append("]");
			return sb.toString();
		}

	}

	static volatile HashMap<String, Boolean> data = new HashMap<String, Boolean>();
	static long time = 0;
	static long time1 = 0;

	static boolean compute(int x) {
		if (x == 1)
			return false;
		if (x == 2)
			return true;

		ShiKuaiGroup s1 = new ShiKuaiGroup(x - 1);
		if (!containsKey(s1)) {
			// System.out.println("compute " + (x - 1) + " first");
			compute(x - 1);
		}
		ShiKuaiGroup skg = new ShiKuaiGroup(x);
		ShiKuaiGroup s2 = new ShiKuaiGroup(x - 2);
		if ((containsKey(s1) && !get(s1)) || (containsKey(s2) && !get(s2))) {
			put(skg.clone(), true);
			return true;
		}

		if (!containsKey(skg)) {
			for (int i = 1; i < (x + 1) / 2; i++) {
				ShiKuaiGroup skg1 = new ShiKuaiGroup(i, x - i - 1);
				boolean flag = compute(skg1);
				if (!flag) {
					put(skg.clone(), true);
					return true;
				}
			}
			for (int i = 1; i < x / 2; i++) {
				ShiKuaiGroup skg1 = new ShiKuaiGroup(i, x - i - 2);
				boolean flag = compute(skg1);
				if (!flag) {
					put(skg.clone(), true);
					return true;
				}
			}
			put(skg.clone(), false);
		}
		return get(skg);

	}

	private static boolean compute(ShiKuaiGroup skg) {
		// System.out.println(skg.toString());
		if (containsKey(skg)) {
			return get(skg);
		}

		ArrayList<ShiKuai> rms = new ArrayList<ShiKuai>();
		for (ShiKuai sk : skg.group) {
			if (sk.value == 1) {
				rms.add(sk);
			}
		}

		if (rms.size() >= 2) {
			skg.group.remove(rms.get(0));
			skg.group.remove(rms.get(1));
			boolean flag = compute(skg);
			skg.group.add(rms.get(0));
			skg.group.add(rms.get(1));
			return flag;
		}

		// ShiKuai s = skg.group.get(0);
		// if (s.value == 1) {
		// ShiKuai s1 = skg.group.get(1);
		// if (s1.value == 1) {
		// boolean flag = compute(skg);
		// skg.group.add(s1);
		// skg.group.add(s);
		// return flag;
		// } else {
		// skg.group.add(s1);
		// skg.group.add(s);
		// }
		// } else {
		// skg.group.add(s);
		// }

		ArrayList<ShiKuai> skss = new ArrayList<ShiKuai>();
		for (ShiKuai shiKuai : skg.group) {
			skss.add(shiKuai);
		}

		for (ShiKuai sk : skss) {
			skg.group.remove(sk);
			for (int i = 0; i < (sk.value + 1) / 2; i++) {
				ArrayList<ShiKuai> sks = sk.splits(i);
				// System.out.println(sk.value + "\t" + sks);
				skg.group.addAll(sks);
				boolean flag = compute(skg);
				for (ShiKuai shiKuai : sks) {
					skg.group.remove(shiKuai);
				}

				if (!flag) {
					skg.group.add(sk);
					put(skg.clone(), true);
					return true;
				}
			}
			skg.group.add(sk);
		}

		for (ShiKuai sk : skss) {
			skg.group.remove(sk);
			for (int i = 0; i < sk.value / 2; i++) {
				ArrayList<ShiKuai> sks = sk.splits2(i);
				skg.group.addAll(sks);
				boolean flag = compute(skg);
				for (ShiKuai shiKuai : sks) {
					skg.group.remove(shiKuai);
				}
				if (!flag) {
					skg.group.add(sk);
					put(skg.clone(), true);
					return true;
				}
			}
			skg.group.add(sk);
		}
		put(skg, false);
		return false;
	}

	static int cnt = 0;

	private static void put(ShiKuaiGroup skg, boolean flag) {
		//if (skg.group.size() == 2) {
			long time2 = System.currentTimeMillis();
			int x = (int) (time2 - time1);
			// if (!flag || x > 60 * 1000)
			//if (!flag) {
				System.out.println(skg.toString() + "\t" + flag + "\t" + x
						+ "\t" + (time2 - time));
				time1 = time2;
			//}
		//}
		cnt++;
		// if (cnt%10000==0)
		// System.out.println(cnt);
		data.put(skg.toString(), flag);
	}

	private static boolean get(ShiKuaiGroup s1) {
		return data.get(s1.toString());
	}

	private static boolean containsKey(ShiKuaiGroup s1) {
		return data.containsKey(s1.toString());
	}

	public static void main(String[] args) {
		time = System.currentTimeMillis();
		time1 = time;
		put(new ShiKuaiGroup(1), false);
		put(new ShiKuaiGroup(2), true);
		put(new ShiKuaiGroup(3), true);
		put(new ShiKuaiGroup(4), false);
		put(new ShiKuaiGroup(5), true);
		put(new ShiKuaiGroup(1, 1), true);
		put(new ShiKuaiGroup(1, 2), true);
		put(new ShiKuaiGroup(2, 2), false);
		System.out.println(compute(20));
		System.out.println(System.currentTimeMillis() - time);
	}
}

算法复杂度较难精确计算,粗略估计,小于n!

    原文作者:田万鹏
    原文地址: https://blog.csdn.net/zhifeidie/article/details/6842652
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞