上一篇:https://blog.youkuaiyun.com/liangsheng_meng/article/details/118925903
1.NIM游戏(异或和为0必败态,不为0后手胜)
2.sg函数(不写了)
3.树上删边游戏
题目:在某一棵树上删除一条边,同时删去所有在删除后不再与根相连的部分
双方轮流操作,无法再进行删除者判定为失败
结论:叶子结点的SG值为0,中间节点的SG值为它的所有子节点的SG值加一后的异或和。
图上删边
题目:选择一个中心节点,在图上上删除一条边,同时删去所有在删除后不再与中心节点相连的部分
双方轮流操作,无法再进行删除者判定为失败
思路:我们可以对无向图做如下改动:将图中的任意一个偶环缩成一个新点,任意一个奇环缩成一个新点加一个新边;所有连到原先环上的边全部改为与新点相连。这样的改动不会影响图的SG 值。
一些题目
1.GCD game
题目:N个数,每次操作可以将一个数除以一个他的约数,轮流进行操作,不能除的人输。
问先手必胜的条件?
结论:质因子个数之和等价与NIM游戏的石子数
所有数所有质因子个数之和异或和不为 0 ,先手胜,为 0 后手胜
2.台阶NIM游戏
题目:现在,有一个 n 级台阶的楼梯,每级台阶上都有若干个石子,其中第 i 级台阶上有 ai 个石子(i≥1)。
两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。
已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
结论:答案的结果只与奇数台阶上的数有关。求位置为奇数的ai的异或和,若异或和等于0,则先手必败,否则先手必胜。
树上台阶NIM(没有做过这种题,也不知道分析的对不对)
题目:类似与台阶NIM,给定一棵树,每个点有点权,两位玩家轮流操作,每次操作可以从一个节点上拿若干个石子放到父节点中(不能不拿)。
已经拿到根节点上的石子不能再拿,最后无法进行操作的人视为失败。
分析:这个我们可以单独拿出一条包含根节点的链来考虑,这个问题就转化为普通的台阶NIM,对结果有影响的只有奇数深度的节点(根节点深度设为0)。
考虑整个局面,将所有奇数深度的节点值异或起来就是整个局面的sg值。
3.硬币游戏
问题:n个硬币,围成一圈,每次操作可以移走1-k个连续硬币
取完的获胜
谁获胜?
思路:可以看出
1、k>=n 先手胜
2、k=1 n奇数先手胜,偶数后手胜利
3、n>k 后手胜(对称性构造)
1.2.都是显然成立,对于3,我们可以考虑一个圆,先手取那段,我们可以对称着取一段,这样一个圆可以划分为偶数段,那么后手必胜
#include<bits/stdc++.h>
using namespace std;
int n,m,t;
int main()
{
scanf("%d",&t);
int cas=0;
while(t--)
{
scanf("%d%d",&n,&m);
printf("Case %d: ",++cas);
if(m==1)
{
if(n&1) printf("first\n");
else printf("second\n");
}
else
{
if(n<=m)printf("first\n");
else printf("second\n");
}
}
return 0;
}
我们也可以考虑一个类似的问题:如果这些硬币不是围成一个圆,而是一条直线,那么也可以类似求解。
4.Game on Sequence
题目:N个数字的序列,第i个位置数字为ai
游戏开始的时候有一个石子在序列的第K个位置
某一时刻,如果石子在第 i 个位置,如存在 j 满足 j>I 并且 ai 和 aj 二进制相差不超过一位,那么就可以移动石子到第j个位置
不能移动者输掉比赛
注意序列中所有数字都在255以内(二进制最多8位)
你需要处理动态的询问:
操作:给序列末尾插入一个k
询问:石子开始时在k这个位置,谁会获胜
思路:我们可以从序列中的数很小下手,因为数字的值域范围很小,所以序列中会出现很多重复的数字,考虑出现重复的数字会有什么影响?
1.如果存在一个a,他可以走到序列后面的一个a,那么此时属于什么状态?(必胜态?必败态?)
2.如果存在一个a , 他可以走到多个b(a!=b),那么我们该如何考虑?
对于问题1,可以分类讨论后面那个1是必胜态还是必败态,得出前面的1必然是必胜态。对于问题2,我们知道,公平组合游戏中,我们并不关心一个状态能否转移到必胜态,又有问题1的结论,我们知道我们只要看每种数字的最后一个就好。
代码:
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=5e5+7;
int a[N];
int last[300];
int st[300],v[300];
vector<int> vi[300];
int dfs(int k)
{
if(v[a[k]]!=0) return st[a[k]];
v[a[k]]=1;
int ans=0;
for(int i=0;i<vi[a[k]].size();i++)
{
int v=vi[a[k]][i];
int p=last[v];
if(p<=k) continue;
ans|=!dfs(p);
}
st[a[k]]=ans;
return ans;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<256;i++)
{
for(int j=0;j<8;j++)
{
int v=i^(1<<j);
vi[i].pb(v);
}
}
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
last[a[i]]=i;
}
for(int i=1;i<=m;i++)
{
int op,k;
scanf("%d%d",&op,&k);
if(op==1)
{
n++;
last[k]=n;
a[n]=k;
}
else
{
if(last[a[k]]!=k)
{
printf("Grammy\n");
continue;
}
memset(v,0,sizeof v);
memset(st,0,sizeof st);
int ans=dfs(k);
if(ans) printf("Grammy\n");
else printf("Alice\n");
}
}
}
其他类似于NIM游戏的游戏(见了会补充)
1.集合-Nim游戏
题目:给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S。
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
思路:每堆石子都看作一个公平组合游戏,整体sg值就是每堆石子sg值得异或和
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=10007;
int k,n;
int s[N],h[N];
int f[N];
int sg(int x)//记忆化搜索求sg值
{
if(f[x]!=-1) return f[x];
unordered_set<int> pp;
for(int i=0;i<k;i++)
{
if(x>=s[i]) pp.insert(sg(x-s[i]));
}
int idx=0;
while(pp.count(idx)) idx++;
return f[x]=idx;
}
int main()
{
scanf("%d",&k);
for(int i=0;i<k;i++) scanf("%d",&s[i]);
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&h[i]);
memset(f,-1,sizeof f);
int sum=0;
for(int i=0;i<n;i++)
{
sum^=sg(h[i]);
}
if(sum) printf("Yes\n");
else printf("No\n");
}
2.拆分-Nim游戏
题目:给定 n 堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为 0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
思路:考虑整个游戏的博弈状态图,对于一堆x个的石子,对他进行操作后会变为两堆不大于x的石子堆。又有在公平组合游戏中,总游戏的sg值是组合为他的游戏的sg值的异或和,所以,对于这堆石子,我们可以知道遍历他所有可以到达的局面,取异或和,就是结果的sg值。
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n;
int f[N];
int sg(int x)
{
if (f[x] != -1) return f[x];
unordered_set<int> S;
for (int i = 0; i < x; i ++ )
for (int j = 0; j <= i; j ++ )
S.insert(sg(i) ^ sg(j));
for (int i = 0;; i ++ )
if (!S.count(i))
return f[x] = i;
}
int main()
{
cin >> n;
memset(f, -1, sizeof f);
int res = 0;
while (n -- )
{
int x;
cin >> x;
res ^= sg(x);
}
if(res) printf("Yes\n");
else printf("No\n");
}
剪纸游戏
题目:
给定一张 N×M 的矩形网格纸,两名玩家轮流行动。在每一次行动中,可以任选一张矩形网格纸,沿着某一行或某一列的格线,把它剪成两部分。首先剪出 1×1 的格纸的玩家获胜。
两名玩家都采取最优策略行动,求先手是否能获胜。
提示:开始时只有一张纸可以进行裁剪,随着游戏进行,纸张被裁剪成 2,3,… 更多张,可选择进行裁剪的纸张就会越来越多。
思路:这个可以看作二维的拆分NIM,思路和他类似,考虑整个公平组合游戏如何由一些子公平组合游戏组成,结果是他们的sg值的mex