G60 Nim游戏 SG函数

视频链接:https://www.bilibili.com/video/BV1eT411B7A8/

1. LOJ 10243. 移棋子游戏

给定一个有 𝑛 个节点和 𝑚 条边的有向无环图,𝑘 个棋子所在的节点编号。
两名玩家交替移动棋子,每次只能将任意一颗棋子沿有向边移到另一个点,无法移动者视为失败。
如果两人都采用最优策略,问先手是否必胜。

思路:每个棋子都是孤立的,𝑘 个棋子拆分成 𝑘 个有向图游戏,
   利用 SG 定理判断即可

时间:O(k*n)

#include <cstdio>
#include <cstring>
#include <set>
using namespace std;

const int N=2005,M=10005;
int n,m,k,a,b,x;
int h[N],to[M],ne[M],tot; //邻接表
int f[N];

void add(int a,int b){
  to[++tot]=b,ne[tot]=h[a],h[a]=tot;
}
int sg(int u){
  // 记忆化搜索
  if(f[u]!=-1) return f[u]; 
  // 把子节点的sg值插入集合
  set<int> S;
  for(int i=h[u];i;i=ne[i]) 
    S.insert(sg(to[i]));
  // mex运算求当前节点的sg值并记忆
  for(int i=0; ;i++) 
    if(!S.count(i)) return f[u]=i;
}
int main(){
  scanf("%d%d%d",&n,&m,&k);
  for(int i=0;i<m;i++)
    scanf("%d%d",&a,&b), add(a,b);
  memset(f,-1,sizeof f); 
  int res=0;
  for(int i=0;i<k;i++)
    scanf("%d",&x),res^=sg(x);
  if(res) puts("win");
  else puts("lose");
}
#include <cstdio>
#include <cstring>
#include <map>
using namespace std;

const int N=2005,M=10005;
int n,m,k,a,b;
int h[N],to[M],ne[M],tot; //邻接表
int f[N];

void add(int a,int b){
  to[++tot]=b,ne[tot]=h[a],h[a]=tot;
}
int sg(int u){
  // 记忆化搜索
  if(f[u]!=-1) return f[u]; 
  // 把子节点的sg值插入集合
  map<int,int> mp;
  for(int i=h[u];i;i=ne[i]) 
    mp[sg(to[i])]=1;
  // mex运算求当前节点的sg值并记忆
  for(int i=0; ;i++) 
    if(!mp.count(i))return f[u]=i; 
}
int main(){
  scanf("%d%d%d",&n,&m,&k);
  for(int i=0;i<m;i++)
    scanf("%d%d",&a,&b), add(a,b);
  memset(f,-1,sizeof f); 
  int res=0;
  for(int i=0;i<k;i++)
    scanf("%d",&a),res^=sg(a);
  if(res) puts("win");
  else puts("lose");
}

2. 集合型 Nim游戏

给定 𝑚 个整数组成的集合 𝑎𝑖,给定 𝑛 堆石子的数量 𝑏𝑖。
两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须是集合 𝑎 中的整数,最后无法进行操作的人视为失败。
如果两人都采用最优策略,问先手是否必胜。

思路:每堆石子都是孤立的,把 𝑛 堆石子看做 𝑛 个有向图游戏

#include <cstring>
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;

const int N=1005, M=10005;
int n,m,x;
int a[N], f[M];

int sg(int x){
  // 记忆化搜索
  if(f[x]!=-1) return f[x];
  // 把子节点的sg值插入集合
  set<int> S;
  for(int i=0;i<m;i++)
    if(x>=a[i]) S.insert(sg(x-a[i]));
  // mex运算求当前节点的sg值并记忆  
  for(int i=0; ;i++)
    if(!S.count(i)) return f[x]=i;
}
int main(){
  cin>>m;
  for(int i=0; i<m; i++)cin>>a[i];
  cin>>n;
  memset(f,-1,sizeof f);
  int res=0;
  for(int i=0; i<n; i++)
    cin>>x, res^=sg(x);
  if(res) puts("Yes");
  else puts("No");
}

3. POJ2311 Cutting Game 

给定一张 𝑛×𝑚 的矩形网格纸,两名玩家轮流行动。
在每一次行动中,可以任选一张矩形网格纸,沿着某一行或某一列的格线,把它剪成两部分。
首先剪出 1×1 的格纸的玩家获胜。
如果两人都采用最优策略,问先手是否必胜。1≤𝑛,𝑚≤200

#include<iostream>
#include<algorithm>
#include<cstring>
#include<set>
using namespace std;

const int N=210;
int n,m;
int f[N][N];

int sg(int a,int b){
  // 记忆化搜索
  if(f[a][b]!=-1) return f[a][b];
  // 把子节点的sg值插入集合
  set<int> S;
  for(int i=2;i<=a-2;i++)
    S.insert(sg(i,b)^sg(a-i,b));
  for(int i=2;i<=b-2;i++)
    S.insert(sg(a,i)^sg(a,b-i));
  // mex运算求当前节点的sg值并记忆  
  for(int i=0; ;i++)
    if(!S.count(i)) return f[a][b]=f[b][a]=i;
}
int main(){
  memset(f,-1,sizeof f);
  while(cin>>n>>m) puts(sg(n,m)?"WIN":"LOSE");
  return 0;
}

 

posted @ 2023-03-31 19:20  董晓  阅读(362)  评论(0编辑  收藏  举报