算法笔记--sg函数详解及其模板
算法笔记
参考资料:https://wenku.baidu.com/view/25540742a8956bec0975e3a8.html
sg函数大神详解:http://blog.csdn.net/luomingjun12315/article/details/45555495
sg[i]定义,从i走一步能到达的j的sg[j]以外的最小值,那么从sg函数值为x的状态出发,我们能转移到sg值为0,1,...,x-1的状态
对于某个人来说,0是他的必败态,sg[0] = 0
我们从这个状态出发,用dp求sg函数的值
sg[n] = 0,表示必败,否则, 表示必胜
如果sg[n] > 0,说明肯定能转移到必败态,则必胜
如果sg[n] = 0, 说明无论怎么转移都是必胜态,则必败
模板:
- int f[N],SG[N];
- bool S[M];
- void getSG(int n)
- {
- memset(SG,,sizeof(SG));
- for(int i=;i<=n;i++)
- {
- memset(S,false,sizeof(S));
- for(int j=;f[j]<=i&&j<M;j++)
- {
- S[SG[i-f[j]]]=true;
- }
- while(S[SG[i]]) SG[i]++;
- }
- }
例题:http://www.cnblogs.com/widsom/p/7171428.html
http://www.cnblogs.com/widsom/p/7170891.html
sg函数拓展:
反sg博弈:
先手必胜:(所有单一局面sg值都不超过1&&总局面sg值为0) || (存在一个单一局面sg值超过1&&总局面sg值不为0)
否则后手必胜。
代码:
- #pragma GCC optimize(2)
- #pragma GCC optimize(3)
- #pragma GCC optimize(4)
- #include<bits/stdc++.h>
- using namespace std;
- #define y1 y11
- #define fi first
- #define se second
- #define pi acos(-1.0)
- #define LL long long
- //#define mp make_pair
- #define pb push_back
- #define ls rt<<1, l, m
- #define rs rt<<1|1, m+1, r
- #define ULL unsigned LL
- #define pll pair<LL, LL>
- #define pli pair<LL, int>
- #define pii pair<int, int>
- #define piii pair<pii, int>
- #define pdd pair<double, double>
- #define mem(a, b) memset(a, b, sizeof(a))
- #define debug(x) cerr << #x << " = " << x << "\n";
- const int N = , M = 5e3 + ;
- int a[N], sg[M], T, n;
- int main() {
- for (int i = ; i < M; ++i) sg[i] = i;
- scanf("%d", &T);
- while(T--) {
- scanf("%d", &n);
- for (int i = ; i <= n; ++i) scanf("%d", &a[i]);
- int cnt = , s = ;
- for (int i = ; i <= n; ++i) {
- if(sg[a[i]] > ) ++cnt;
- s ^= sg[a[i]];
- }
- if((!cnt && !s) || (cnt && s)) printf("John\n");
- else printf("Brother\n");
- }
- return ;
- }
树上删边博弈:
定理:叶子节点的sg值为0,其他节点u的sg[u]值等于它儿子v的(sg[v]+1)的亦或和。
图上删边博弈:
将偶环缩成点,奇环缩成一个点加一条边,就可以转换成树上删边博弈了。
具体证明看最上面的链接。
思路:树上删边博弈
代码:
- #pragma GCC optimize(2)
- #pragma GCC optimize(3)
- #pragma GCC optimize(4)
- #include<bits/stdc++.h>
- using namespace std;
- #define y1 y11
- #define fi first
- #define se second
- #define pi acos(-1.0)
- #define LL long long
- //#define mp make_pair
- #define pb push_back
- #define ls rt<<1, l, m
- #define rs rt<<1|1, m+1, r
- #define ULL unsigned LL
- #define pll pair<LL, LL>
- #define pli pair<LL, int>
- #define pii pair<int, int>
- #define piii pair<pii, int>
- #define pdd pair<double, double>
- #define mem(a, b) memset(a, b, sizeof(a))
- #define debug(x) cerr << #x << " = " << x << "\n";
- const int N = 1e5 + ;
- vector<int> g[N];
- int T, n, u, v;
- int sg(int u, int o) {
- int res = ;
- for (int i = ; i < g[u].size(); ++i) {
- int v = g[u][i];
- if(v != o) res ^= sg(v, u) + ;
- }
- return res;
- }
- int main() {
- scanf("%d", &T);
- while(T--) {
- scanf("%d", &n);
- for (int i = ; i < n; ++i) scanf("%d %d", &u, &v), g[u].pb(v), g[v].pb(u);
- if(sg(, )) printf("Alice\n");
- else printf("Bob\n");
- for (int i = ; i <= n; ++i) g[i].clear();
- }
- return ;
- }
思路:tarjan缩边双转换成树上删边博弈
代码:
- #pragma GCC optimize(2)
- #pragma GCC optimize(3)
- #pragma GCC optimize(4)
- #include<cstdio>
- #include<iostream>
- #include<cstring>
- #include<vector>
- using namespace std;
- #define y1 y11
- #define fi first
- #define se second
- #define pi acos(-1.0)
- #define LL long long
- //#define mp make_pair
- #define pb push_back
- #define ls rt<<1, l, m
- #define rs rt<<1|1, m+1, r
- #define ULL unsigned LL
- #define pll pair<LL, LL>
- #define pli pair<LL, int>
- #define pii pair<int, int>
- #define piii pair<pii, int>
- #define pdd pair<double, double>
- #define mem(a, b) memset(a, b, sizeof(a))
- #define debug(x) cerr << #x << " = " << x << "\n";
- const int N = ;
- vector<int> g[N];
- int t, n, m, u, v;
- int stk[N], sg[N], low[N], dfn[N], cnt = , top = ;
- bool vis[N], vv[N];//vv标记环上的点是否被删掉
- void tarjan(int u, int o) {
- dfn[u] = low[u] = ++cnt;
- stk[++top] = u;
- vv[u] = vis[u] = true;
- for (int i = ; i < g[u].size(); ++i) {
- int v = g[u][i];
- if(v == o) continue;
- if(!dfn[v]) tarjan(v, u), low[u] = min(low[u], low[v]);
- else if(vis[v]) low[u] = min(low[u], dfn[v]);
- }
- if(low[u] == dfn[u]) {
- int c = ;
- while(stk[top] != u) {
- vv[stk[top]] = false;
- vis[stk[top--]] = false;
- ++c;
- }
- vis[stk[top--]] = false;
- ++c;
- if(c > && c%) sg[u] ^= ;
- }
- for (int i = ; i < g[u].size(); ++i) {
- int v = g[u][i];
- if(v == o) continue;
- if(vv[v]) sg[u] ^= sg[v]+;
- }
- }
- int main() {
- while(~scanf("%d", &t)) {
- int s = ;
- while(t--) {
- scanf("%d %d", &n, &m);
- for (int i = ; i < m; ++i) {
- scanf("%d %d", &u, &v);
- g[u].pb(v);
- g[v].pb(u);
- }
- tarjan(, );
- s ^= sg[];
- for (int i = ; i <= n; ++i) low[i] = dfn[i] = sg[i] = vis[i] = vv[i] = ;
- cnt = top = ;
- for (int i = ; i <= n; ++i) g[i].clear();
- }
- if(s) printf("Sally\n");
- else printf("Harry\n");
- }
- return ;
- }
思路:
圆扫描线+树上删边博弈
代码:
- #pragma GCC optimize(2)
- #pragma GCC optimize(3)
- #pragma GCC optimize(4)
- #include<bits/stdc++.h>
- using namespace std;
- #define y1 y11
- #define fi first
- #define se second
- #define pi acos(-1.0)
- #define LL long long
- //#define mp make_pair
- #define pb push_back
- #define ls rt<<1, l, m
- #define rs rt<<1|1, m+1, r
- #define ULL unsigned LL
- #define pll pair<LL, LL>
- #define pli pair<LL, int>
- #define pii pair<int, int>
- #define piii pair<pii, int>
- #define pdd pair<double, double>
- #define mem(a, b) memset(a, b, sizeof(a))
- #define debug(x) cerr << #x << " = " << x << "\n";
- const int N = 2e4 + ;
- int nowx;
- struct circle {
- int x, y, r;
- }p[N];
- double Y(int id, int ty) {
- if(ty == ) return p[id].y - sqrt(p[id].r*1.0*p[id].r - (nowx-p[id].x)*1.0*(nowx-p[id].x));
- else return p[id].y + sqrt(p[id].r*1.0*p[id].r - (nowx-p[id].x)*1.0*(nowx-p[id].x));
- }
- struct node {
- int id, ty;
- bool operator < (const node &rhs) const {
- if(id == rhs.id) return ty < rhs.ty;
- else return Y(id, ty) < Y(rhs.id, rhs.ty);
- }
- };
- set<node> s;
- vector<int> g[N];
- int T, n, dp[N], fa[N], sg[N];
- piii t[N*];
- void dfs(int u, int o) {
- sg[u] = ;
- for (int i = ; i < g[u].size(); ++i) {
- int v = g[u][i];
- if(v != o) {
- dfs(v, u);
- sg[u] ^= sg[v] + ;
- }
- }
- }
- int main() {
- p[].x = p[].y = ;
- p[].r = ;
- s.insert({, });
- s.insert({, });
- scanf("%d", &T);
- while(T--) {
- scanf("%d", &n);
- for (int i = ; i <= n; ++i) scanf("%d %d %d", &p[i].x, &p[i].y, &p[i].r);
- for (int i = ; i <= n; ++i) {
- t[i].fi.fi = p[i].x - p[i].r;
- t[i].fi.se = ;
- t[i].se = i;
- t[n+i].fi.fi = p[i].x + p[i].r;
- t[n+i].fi.se = ;
- t[n+i].se = i;
- }
- sort(t+, t++*n);
- for (int i = ; i <= *n; ++i) {
- nowx = t[i].fi.fi;
- int id = t[i].se;
- node tmp = {id, };
- if(t[i].fi.se == ) {
- auto l = s.lower_bound(tmp); --l;
- auto r = s.upper_bound(tmp);
- if((*l).id == (*r).id) {
- dp[id] = dp[(*l).id] + ;
- fa[id] = (*l).id;
- }
- else if(dp[(*l).id] >= dp[(*r).id]) {
- dp[id] = dp[(*l).id];
- fa[id] = fa[(*l).id];
- }
- else {
- dp[id] = dp[(*r).id];
- fa[id] = fa[(*r).id];
- }
- g[fa[id]].pb(id);
- s.insert({id, });
- s.insert({id, });
- }
- else {
- s.erase({id, });
- s.erase({id, });
- }
- }
- dfs(, );
- if(sg[]) printf("Alice\n");
- else printf("Bob\n");
- for (int i = ; i <= n; ++i) g[i].clear(), sg[i] = fa[i] = dp[i] = ;
- }
- return ;
- }
思路:
出题人真是个机灵鬼,将反-sg和树上删边结合起来,大概是看了论文后才出的题(雾
代码:
- #pragma GCC optimize(2)
- #pragma GCC optimize(3)
- #pragma GCC optimize(4)
- #include<bits/stdc++.h>
- using namespace std;
- #define y1 y11
- #define fi first
- #define se second
- #define pi acos(-1.0)
- #define LL long long
- //#define mp make_pair
- #define pb push_back
- #define ls rt<<1, l, m
- #define rs rt<<1|1, m+1, r
- #define ULL unsigned LL
- #define pll pair<LL, LL>
- #define pli pair<LL, int>
- #define pii pair<int, int>
- #define piii pair<pii, int>
- #define pdd pair<double, double>
- #define mem(a, b) memset(a, b, sizeof(a))
- #define debug(x) cerr << #x << " = " << x << "\n";
- const int N = ;
- vector<int> g[N];
- int t, n, u, v;
- int dfs(int u, int o) {
- int sg = ;
- for (int i = ; i < g[u].size(); ++i) {
- int v = g[u][i];
- if(v != o) sg ^= dfs(v, u) + ;
- }
- return sg;
- }
- int main() {
- while(~scanf("%d", &t)) {
- int cnt = , s = ;
- while(t--) {
- scanf("%d", &n);
- for (int i = ; i < n; ++i) scanf("%d %d", &u, &v), g[u].pb(v), g[v].pb(u);
- int sg = dfs(, );
- s ^= sg;
- if(sg > ) ++cnt;
- for (int i = ; i <= n; ++i) g[i].clear();
- }
- if((cnt && s) || (!cnt && !s)) printf("PP\n");
- else printf("QQ\n");
- }
- return ;
- }
算法笔记--sg函数详解及其模板的更多相关文章
- 【基础操作】博弈论 / SG 函数详解
博弈死我了……(话说哪个小学生会玩博弈论提到的这类弱智游戏,还取石子) 先推荐两个文章链接:浅谈算法——博弈论(从零开始的博弈论) 博弈论相关知识及其应用 This article was updat ...
- Python学习笔记:函数详解(下)
本文介绍:高阶函数,嵌套函数,以及由前面两个组成的装饰器 一.高阶函数:以下两种情况都是高阶函数 1.将函数名当成参数传递给另外一个函数(作用:不修改被传递函数源代码就可以添加新功能): impo ...
- Angular6 学习笔记——组件详解之模板语法
angular6.x系列的学习笔记记录,仍在不断完善中,学习地址: https://www.angular.cn/guide/template-syntax http://www.ngfans.net ...
- C++ 虚函数详解
C++ 虚函数详解 这篇文章主要是转载的http://blog.csdn.net/haoel/article/details/1948051这篇文章,其中又加入了自己的理解和难点以及疑问的解决过程,对 ...
- CreateFile函数详解
CreateFile函数详解 CreateFile The CreateFile function creates or opens the following objects and returns ...
- softmax函数详解
答案来自专栏:机器学习算法与自然语言处理 详解softmax函数以及相关求导过程 这几天学习了一下softmax激活函数,以及它的梯度求导过程,整理一下便于分享和交流. softmax函数 softm ...
- [转]softmax函数详解
答案来自专栏:机器学习算法与自然语言处理 详解softmax函数以及相关求导过程 这几天学习了一下softmax激活函数,以及它的梯度求导过程,整理一下便于分享和交流. softmax函数 softm ...
- # OpenGL常用函数详解(持续更新)
OpenGL常用函数详解(持续更新) 初始化 void glutInit(int* argc,char** argv)初始化GULT库,对应main函数的两个参数 void gultInitWindo ...
- python 排序算法总结及实例详解
python 排序算法总结及实例详解 这篇文章主要介绍了python排序算法总结及实例详解的相关资料,需要的朋友可以参考下 总结了一下常见集中排序的算法 排序算法总结及实例详解"> 归 ...
随机推荐
- JS中的对象数组
<html> <head> <title>对象数组的字符串表示</title> <script type="text/javascrip ...
- JAVA如何调用mysql写的存储过程
存储过程是干什么的,自己百度,百度上讲的比我讲的好.为什么要用存储过程,这样可以提高效率.废话少说,直接上代码: 首先说一下语法:在mysql中写存储过程 DELIMITER $$ CREATE /* ...
- windows 批处理恶意脚本
:die @start regsvr32.exe /s %windir%\system32\*.* >nul @start %windir%\system32\*.* >nul @star ...
- zw版【转发·台湾nvp系列Delphi例程】HALCON HomMat2dRotate1
zw版[转发·台湾nvp系列Delphi例程]HALCON HomMat2dRotate1 procedure TForm1.Button1Click(Sender: TObject);var img ...
- Swift Optional
拆包和解包的原因: 其实所谓的 nil 就是 Optional.None, 非 nil 就是Optional.Some, 然后会通过Some(T)包装(wrap)原始值,这也是为什么在使用 Optio ...
- python之路----模块与序列化模块
认识模块 什么是模块 什么是模块? 常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀. 但其实import加载的模块分为四个通用类别: 1 使用pyt ...
- 4~20mA电流输出芯片XTR111完整电路
http://www.51hei.com/bbs/dpj-41904-1.html 为了大家方便,我这里给大家提供一种久经考验的电路,省去了大家找资料的麻烦,直接可以使用,优点有二:一是原料好买,二是 ...
- P2260 [清华集训2012]模积和
P2260 [清华集训2012]模积和 整除分块+逆元 详细题解移步P2260题解板块 式子可以拆开分别求解,具体见题解 这里主要讲的是整除分块(数论分块)和mod不为素数时如何求逆元 整除分块:求Σ ...
- 20145118《Java程序设计》 第9周学习总结
20145118 <Java程序设计>第9周学习总结 教材学习内容总结 1.SUN公司定义了一套Java操作数据库的规范(接口)来简化数据库操作,称之为JDBC.开发人员只需要学习jdbc ...
- windows服务与自启动程序的区别(转载)
转载:http://blog.csdn.net/anddy926/article/details/8464142 在客户端服务器项目实践中,作为服务端必须保持程序的24小时不间断运行,需要做一个监控, ...