桌子上放着两堆糖果,Matrix67和Shadow轮流对这些糖果进行操作。在每一次操作中,操作者需要吃掉其中一堆糖果,并且把另一堆糖果分成两堆(可以不相等)留给对方操作。游戏如此进行下去,糖果数会越来越少,最后必将出现这样一种情况:某人吃掉一堆糖果后发现另一堆里只剩一块糖果不能再分了。游戏规定此时该操作者吃掉最后这一块糖果从而取胜。
这个游戏是不公平的。对于任意一种初始状态,总有一方有必胜策略。所谓有必胜策略是指,无论对方如何操作,自己总有办法取胜。
Matrix67和Shadow将进行10次游戏,每一次游戏中总是Matrix67先进行操作。Matrix67想知道每一次游戏中谁有必胜策略。
直接分析,真是不好做,分析化找规律感觉挖掘的性质不够多,分析的不够全。
但是发现这题可以打表出来,以此找规律!
打表实际上就是暴力喽,f【i】表示分大小为i的堆,先手是否有必胜策略。经过分析发现f【i】是从f【1~i/2】推过来的。
那么博弈论dp搞一搞,70分就有了。以下有注意点。
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
int n,m;
bool f[12005];
void init()
{
f[1]=true;
for (int i=2;i<=10000;i++)
{
for (int j=1;j<=i/2;j++)//枚举它分成的所有情况,在所有情况中选对于当前先手的最优,这也就是博弈论dp的思路。
{
int x=j,y=i-j;
f[i]=f[i]||(!(f[x]||f[y]));//这里的“非”必须是!号,~号是不行的!!!!!!
}
}
}
int main()
{
init();
for (int i=1;i<=200;i++) printf("%d %d\n",i,f[i]);
while (scanf("%d%d",&n,&m)!=EOF)
{
if (f[n]||f[m]) printf("Matrix67\n");else printf("Shadow\n");
}
return 0;
}
打出f【i】的表,发现。。。五个为一循环,不用dp了,o(1)直接算就行
1 1
2 0
3 0
4 1
5 1
6 1
7 0
8 0
9 1
10 1
11 1
12 0
13 0
14 1
15 1
16 1
17 0
18 0
19 1
20 1
21 1
22 0
23 0
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
bool f(int n)
{
n=n%5;
switch(n)
{
case 0:
case 1:
case 4:return true;
case 2:
case 3:return false;
}
}
char s1[10005],s2[10005];
int len1,len2;
int main()
{
while (scanf("%s%s",s1,s2)!=EOF)
{
len1=strlen(s1);len2=strlen(s2);
if (f(s1[len1-1]-'0')||f(s2[len2-1]-'0')) printf("Matrix67\n");else printf("Shadow\n");
}
return 0;
}
总结
1:博弈论问题(这里适合nim游戏)的主体思路
一 :主要思路就是从小范围情况向大范围情况推,由此进行归纳和总结
二:分情况手推小范围的数据,模拟nim游戏的运行,在计算的过程中感受后来状态和之前状态之间的关系。
三:有些题看着数据范围过大(此题的数达到10000位),可能最终答案可以从输入的这些数据本身找答案,不必想像弈论dp从前忘后推了,实际上这就是要我们找规律
2:这题规律,真心不好推,直接找规律不行,分析题目性质找规律也很难,怎么办?
写暴力,输出答案找规律。考场上也是要这样,写暴力程序找规律(这里是用博弈论dp写了70分的数据,10000的数据就是给这种做法的)。
3:博弈论dp才是更通用的做法,找规律只是对这种做法的优化而已(感觉很多给数据70分和100分的两种,70分就是好不容易想到的主要想法,而100分就是对这个算法的优化),博弈论dp从小往大推,一定要保证先后手都是最优策略,当前=(所有情况的最差策略(保证后手最优策略))中的最优策略(保证先手最优策略)。//只可意会不可言传,蓝书67页”sum游戏“的做法可将这个思想进行很好的概括,这题很便于意会了!