// dfs序遍历DAG图求SG函数 intgetSg(int cur) { //如果有值,直接返回(记忆化搜索) if (sg[cur] != -1) return sg[cur]; //如果没有,那就找 //用于标记 bool vis[N] = {false}; //遍历当前结点的后继结点,dfs序求出他们的sg函数,并标记 for (int i = 0; i < a[cur].size(); i++) { int next = a[cur][i]; int pos = getSg(next); //为true的pos就是当前cur结点的后继的sg值 vis[pos] = true; }
// mex函数,找出最小的不在集合内的自然数 for (int i = 0; i < n; i++) { if (vis[i] == false) { sg[cur] = i; break; } } return sg[cur]; }
非DFS,一次性打表
int f[15] = {1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987}; bool vis[N]; int sg[N]; voidgetSg(int n) { memset(sg, 0, sizeof(sg)); // sg[0]=0,所以循环从1开始 for (int i = 1; i <= n; i++) { memset(vis, 0, sizeof(vis)); for (int j = 0; j <= n && f[j] <= i; j++) vis[sg[i - f[j]]] = true; //当前节点可以转到的下一状态标记 for (int j = 0; j <= n; j++) { //查询当前后继状态SG值中最小的非零值 if (vis[j] == false) { sg[i] = j; break; } } } }
三、题解一览
1.博弈论练习 - H - A Chess Game
题目标签
博弈论;SG函数
题目描述
给定一个DAG图,以及一些棋子,判断是否能获胜
题目解析
经典的SG函数的应用。
用dfs序遍历一个DAG图求解SG函数
先判断值是否存在(记忆化搜索)
当前结点能走的所有结点并dfs,打上标记
mex函数找出最小不存在集合内的自然数
通过代码
#include<iostream> #include<algorithm> #include<vector> #include<cstring> #define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) #define N 1100 usingnamespace std; int n; int sg[N]; vector<int> a[N]; // dfs序遍历DAG图求SG函数 intgetSg(int cur) { //如果有值,直接返回(记忆化搜索) if (sg[cur] != -1) return sg[cur];
//如果没有,那就找 //用于标记 bool vis[N] = {false}; //遍历当前结点的后继结点,dfs序求出他们的sg函数,并标记 for (int i = 0; i < a[cur].size(); i++) { int next = a[cur][i]; int pos = getSg(next); //为true的pos就是当前cur结点的后继的sg值 vis[pos] = true; }
// mex函数,找出最小的不在集合内的自然数 for (int i = 0; i < n; i++) { if (vis[i] == false) { sg[cur] = i; break; } } return sg[cur]; }
intmain() { ios; while (cin >> n) { bool v[N]; memset(sg, -1, sizeof(sg)); memset(v, 0, sizeof(v)); for (int i = 0; i < n; i++) { int t; cin >> t; a[i].clear(); for (int j = 0; j < t; j++) { int temp; cin >> temp; a[i].push_back(temp); v[i] = true; } } int q; while (cin >> q && q != 0) { int ans = 0; while (q--) { int temp; cin >> temp; ans ^= getSg(temp); } if (ans == 0) cout << "LOSE" << endl; else cout << "WIN" << endl; } } }
#include<bits/stdc++.h> #define ull unsigned long long #define ll long long #define N 1001 #define inf 0x3f3f3f usingnamespace std; voidsolve(ll n){ ll a[101]={0}; ll sum=0; ll ans=0; for (ll i = 0; i < n; i++) { cin>>a[i]; sum^=a[i]; } for (ll i = 0; i < n; i++) { if(a[i]>(sum^a[i])){ ans++; } } cout<<ans<<endl; }
#include<bits/stdc++.h> usingnamespace std; constint N = 10010; int f[15] = {1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987}; int s[N]; int sg[N]; voidgetSg(int n){ memset(sg,0,sizeof(sg)); //sg[0]=0,所以循环从1开始 for(int i=1;i<=n;i++){ memset(s,0,sizeof(s)); for(int j=0;j<=n&&f[j]<=i;j++) s[sg[i-f[j]]]=1;//当前节点可以转到的下一状态标记 for(int j=0;j<=n;j++){ //查询当前后继状态SG值中最小的非零值 if(!s[j]){ sg[i]=j; break; } } } } intmain() { int a, b, c; getSg(1001); while (cin >> a >> b >> c && a != 0 && b != 0 && c != 0) { memset(f, -1, sizeof(f)); //初始化f均为-1,方便在sg函数中查看x是否被记录过 int res = 0; res ^= sg[a]; res ^= sg[b]; res ^= sg[c]; //观察异或值的变化,基本原理与Nim游戏相同
if (res != 0) printf("Fibo\n"); else printf("Nacci\n"); } return0;// }
6.并查集练习 - C - The Suspects
题目标签
并查集
题目描述
某地爆发了疫情
给出一堆序列,表示这些人曾存在同一场所,为密接
已知0号为携带者,所有和他为密接的人都会被感染
求最后感染的总人数
题目解析
记录结点数量的并查集,统计0号结点所在位置的并查集的大小即可
记录结点数量:每个并查集的数量记录在最大祖先结点上,所有人初始化为1
每次合并时,让合并后的祖先更新size大小
查询时查询size[find(0)]即可
通过代码
#include<bits/stdc++.h> usingnamespace std; int fa[30005]; int size[30005]; inlineintfind(int x) { while (x != fa[x]) x = fa[x] = fa[fa[x]]; return x; } voidmerge(int x, int y) { x=find(x); y=find(y); if(x==y) return; fa[y] = x; size[x]+=size[y]; } intmain() { int n, m; while (cin >> n >> m && n+m!=0) { memset(fa,0,sizeof(fa)); memset(size,0,sizeof(size)); for (int i = 0; i < n; i++) { fa[i] = i; size[i]=1; } for (int i = 0; i < m; i++) { int num;cin>>num; int father;cin>>father; for (int j = 1;j < num; j++)//合并所有 { int son;cin>>son; merge(father,son); } }// cout<<size[find(0)]<<endl; } }
7.并查集练习 - D - Find them, Catch them
题目标签
并查集
题目描述
有两个帮派的人分别为龙和虎
现有n人,编号为1-n,要么隶属于龙,要么隶属于虎
现在每行给出一个操作符 和 两个数
D表示两个人不是同一帮派的
A表示查询这两个人是否为同一帮派的
题目解析
使用到种类并查集
普通并查集是以朋友的朋友还是朋友的思路串在一起
而种类并查集是以敌人的敌人是朋友联系在一起
所以要开两倍数组,以n为分界线
表示朋友时,那么在分界线内合并
即merge(a,b);merge(a+n,b+n)
表示敌人时,那么跨过分界线合并
即merge(a+n,b);merge(a,b+n)
若有三种不同种类,则开三倍数组
通过代码
#include<bits/stdc++.h> #define ll long long usingnamespace std; int fa[200010]; inlineintfind(int x) { while (x != fa[x]) x = fa[x] = fa[fa[x]]; return x; } voidmerge(int x, int y) { fa[find(y)] = find(x); } intmain() { int T; scanf("%d",&T); while (T--) { int m, n; scanf("%d%d",&n,&m); for (int i = 0; i <=2*n; i++) { fa[i] = i; } for (int i = 0; i < m; i++) { char op[2]; int b, c; scanf("%s %d %d",op,&b,&c); if (op[0] == 'D') //合并不同类 { //合并 merge(b + n, c); merge(b, c + n); } elseif(op[0] == 'A') { if (find(b) == find(c)) { printf("In the same gang.\n"); } elseif (find(b + n) == find(c)) { printf("In different gangs.\n"); } else { printf("Not sure yet.\n");// } } } } }