Topcoder 16634(SRM793 Div.1 C) TinyChessboardNim 题解

题目链接:TinyChessboardNim

题目大意:给定一个 (2 imes 2) 的矩形 (egin{bmatrix} a & b \ c & d end{bmatrix}),你每一次可以选择其中的一行或者一列,在选定一个正整数 (x),使得这你选择的两个数同时减去 (x),需要保证剩下的所有数非负,先后手轮流操作,问能够使得先手必胜的第一步操作有多少种。


题解:我们将原题中的 (c,d) 互换一下,就变成了 (egin{bmatrix} a & b \ d & c end{bmatrix}),这样的话就是按照顺时针排列了,然后给出本题的重要性质:假设 (a) 为矩阵中的最大值,那么如果 (b eq d) 先手必胜(反过来先手不一定必败)。证明考虑归纳:不妨假设 (b>d),那么我们可以同时给 (a,b) 减去 (b-d) 那么就达到了 (b=d) 的情况,若此时即为必败态,那么证明完毕。否则根据结论,后手也需要使得与最大值相邻的两个数相等,然而此时后手只有一种行动方式,就是将 (a,b) 各自取走 (a-c)(若 (cge b) 依然是后手必败,因为此时先手一定可以模拟后手的操作),然而此时到达的状态依然是先手可以一步到达的(先手只需要一次性多减一些就可以了),所以先手必胜。

上文提到了在 (cge b) 的情况下若 (b=d) 那么先手必败,接下来考虑 (c<b) 的情况,容易发现此时先后手都只剩下一条路可走,所以直接计算步数判断奇偶性即可。

时间复杂度 (O(1))

#include <vector>
#include <cstdio>
#include <algorithm>
using namespace std;
struct TinyChessboardNim{
	int ans;
	void rotate(vector<int> &a){
		int tmp=a[0];
		for(int i=1;i<(int)a.size();i++){
			a[i-1]=a[i];
		}
		a.back()=tmp;
	}
	int get_max(vector<int> &a){
		int ans=a[0];
		for(int i=1;i<(int)a.size();i++){
			ans=max(ans,a[i]);
		}
		return ans;
	}
	bool win(vector<int> a){
		while(a[0]!=get_max(a)){
			rotate(a);
		}
		if(a[1]!=a[3]){
			return 1;
		}
		if(a[2]>=a[1]){
			return 0;
		}
		int delta=a[0]-a[2];
		return a[1]/delta!=a[2]/delta;
	}
	void check(vector<int> rice,int x){
		if(x>0&&rice[0]>=x&&rice[1]>=x){
			vector<int> a=rice;
			a[0]-=x;
			a[1]-=x;
			ans+=!win(a);
		}
	}
	int countWinningMoves(vector<int> rice){
		swap(rice[2],rice[3]);
		ans=0;
		for(int i=0;i<4;i++){
			check(rice,rice[1]-rice[3]);
			if(rice[1]-rice[3]!=rice[0]-rice[2]){
				check(rice,rice[0]-rice[2]);
			}
			rotate(rice);
		}
		return ans;
	}
};
原文地址:https://www.cnblogs.com/withhope/p/14250330.html