博弈论嘻嘻
https://www.cnblogs.com/Simon-X/p/5905960.html
这个介绍得很好,生动形象得介绍了sg。
http://hihocoder.com/contest/hiho46/problem/1 这个呢就是官方点得解释,emmm我选择上面那个
chess hdu 5724
题意:有一个n行20列的棋盘,棋盘上分布着一些棋子,A、B两人轮流下棋,A先手,每次操作可以将某个棋子放到自己右边的第一个空位(也就是说右边如果已经有子,可以跳过它,没有就右移一步),但最多20列,绝对不能超过棋盘,无棋可走的输。
题解:进行状态压缩,bit来表示在一行中一个点有没有棋子,有棋子为1,没有棋子为0,0到(2^20-1)就代表全了所有的可能。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; int SG[(1<<20)+100],book[30]; void get_SG() { for(int i=0;i<(1<<20);i++) { memset(book,-1,sizeof(book)); int last=-1; for(int j=0;j<20;j++) { if(!((i>>j)&1)) ///空格在最右的位置 last=j; if((i>>j)&1) ///最右棋子的位置 { if(last!=-1) book[SG[(i^(1<<j))^(1<<last)]]=true; ///后继状态标记 ///找到最右边的棋子以及可移动的空格,然后互换成后继并标记,互换后一定比互换前的值小,因为我们是先找空格再找棋子的 /// } } int item=0; while(book[item]!=-1) item++; ///找出最小的不属于这个集合的非负整数 // printf("item=%d\n",item); SG[i]=item; } } int main() { memset(SG,0,sizeof(SG)); get_SG(); int ncase; scanf("%d",&ncase); while(ncase--) { int n,m,ans=0,item; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&m); item=0; for(int j=1;j<=m;j++){ int x; scanf("%d",&x); item^=1<<(20-x); } // printf("%d\n",SG[item]); ans^=SG[item]; } // printf("ans=%d\n",ans); if(ans) printf("YES\n"); else printf("NO\n"); } return 0; }
Doubloon Game
题意:给你SS个石子和一个数字kk,每次只能取kk的幂次的石子数,如:1,k,k2...1,k,k2...,谁先取完谁赢,问你最少取多少个可以获得胜利,即可以使对手面临必败面。
第一开始是直接想直接暴力4求出sg值,但是范围是1e9,所以不可以直接求打表
那么,想想就知道打个找下规律了,我们发现
当k为奇数,则010101分布
当k为偶数,则有个循环节,那么直接判断即可
#include<bits/stdc++.h> using namespace std; int sg[1010],book[1010]; int read(){ char c=getchar();int x=0,f=1; while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();} return x*f; } void getsg() { sg[0]=0; for(int i=1;i<=1000;i++) { memset(book,0,sizeof(book)); for(int j=1;j<=i;j*=6) book[sg[i-j]]=1; for(int j=0;;j++) if(!book[j]) { sg[i]=j; break; } } } int main() { int t;t=read(); while(t--) { int n=read(),k=read(); if(k%2) { if(n%2) puts("1"); else puts("0"); } else { int c=n%(k+1); if(c<k) { if(c%2) puts("1"); else puts("0"); } else { int flag=0; for(int j=k;j<=n;j*=k) { int x=n-j; c=x%(k+1); if(!(c%2)) { flag=j; break; } } if(flag) printf("%d\n",flag); else puts ("0"); } } } return 0; }
mine
扫雷游戏,点开一个方,如果空白,则将周围8格的数字和空白翻开,如果过程中翻开空格,则继续由该空格翻开周围8格(显然dfs嘛)点击空格方格,空格方格消失并且每个空方格连接的数字方格也消失,问你和对手已经事先知道所有的雷在哪,
谁第一个碰到雷谁gg。
在一盘游戏
中,一个格子不可能被翻开两次,说明任意两块空地不会包含相同的格子。
这样我们可以把一大块空白区域当作一堆石子,单独的数字就当作一块石子。
就可以分为多个小游戏了。
当空地旁边没连任何数字的时候,sg = 1(直接转移到 0)。如果有一个
数字,点空地可以转移到 0,点数字可以转移到 1,所以 sg = 2。有 2 个数
字点空地转移到 0,点数字转移到 2,所以 sg = 1。
以此类推,空地旁边有奇数个数字的时候,sg = 2,否则 sg = 1。
剩下的没与空地相连的数字,每个的 sg 都是 1。
那么将所有空地的 sg 异或起来,再异或 (不与空地相连的数字个数对 2
取模),等于零输出后手赢,大于 0 输出先手赢即可。
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <queue> #define MP make_pair using namespace std; typedef pair<int ,int > P; queue<P>que; const int maxx = 1e3+10; int dx[]={0,0,-1,-1,-1,1,1,1}; int dy[]={1,-1,0,-1,1,0,1,-1}; int mp[maxx][maxx]; bool book[maxx][maxx]; int n,m,k; int read() { int f=1,x=0; char c=getchar(); while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); } while (c>='0'&&c<='9') { x=(x<<3)+(x<<1)+c-'0'; c=getchar(); } return x*f; } bool docheck(int x,int y){ if(x<0||x>=n||y<0||y>=m||mp[x][y]==-1) return false; return true; } int bfs(int x,int y){ que.push(MP(x,y)); book[x][y]=1; int num=1; while(!que.empty()){ P top = que.front(); que.pop(); for(int i=0;i<8;i++){ int tmpx = top.first+dx[i]; int tmpy = top.second+dy[i]; if(!docheck(tmpx,tmpy)||book[tmpx][tmpy]) continue; if(mp[tmpx][tmpy]>0) num++; if(mp[tmpx][tmpy]==0) que.push(MP(tmpx,tmpy)); book[tmpx][tmpy]=1; } } return num; } int main() { int T,p=0; // T=read(); scanf("%d",&T); while(T--){ // int n=read(),m=read(),k=read(); scanf("%d%d%d",&n,&m,&k); memset(book,0,sizeof(book)); memset(mp,0,sizeof(mp)); for(int i=0;i<k;i++){ int x,y; scanf("%d%d",&x,&y); mp[x][y]=-1; for(int j=0;j<8;j++){ int tmpx=x+dx[j]; int tmpy=y+dy[j]; if(docheck(tmpx,tmpy)) mp[tmpx][tmpy]=1; } } int ans = 0; for(int i=0;i<n;i++){ for(int j=0;j<m;j++){ if(mp[i][j]==0&&!book[i][j]) ans^=2-bfs(i,j)%2; } } for(int i=0;i<n;i++){ for(int j=0;j<m;j++) if(mp[i][j]==1&&!book[i][j]) ans^=1; } printf("Case #%d: ",++p); if(ans) puts("Xiemao"); else puts("Fanglaoshi"); } return 0; }
HDU1404-sg
意思就是给你长度不大于6的数字
然后你和对手两个操作,要么把该数字例如3变得一个更小的数字,2 1 0,要么选择一个数字为0的,然后把0左边包括这个0一起删掉,最后把所有数字都删除的获胜
题解:
1是必败点那么所有被操作成1的数都是必胜点,以此类推由必败点按找游戏的规则反方向推出所有的必胜点。
反向推出好像之前也有一道,我也是不会嘻嘻嘻。
#include <iostream> #include <cstdio> #include <string> #include <string.h> #include <map> #include <vector> #include <cstdlib> #include <algorithm> #include <cmath> #include <queue> #include <set> #include <stack> #include <functional> #include <fstream> #include <sstream> #include <iomanip> #include <numeric> #include <cassert> #include <bitset> #include <stack> #include <ctime> #include <list> #define INF 0x7fffffff #define max3(a,b,c) (max(a,b)>c?max(a,b):c) #define min3(a,b,c) (min(a,b)<c?min(a,b):c) #define mem(a,b) memset(a,b,sizeof(a)) using namespace std; bool sg[1000000]; int get_length(int n)//得到整数n的位数 { if(n/100000) return 6; if(n/10000) return 5; if(n/1000) return 4; if(n/100) return 3; if(n/10) return 2; return 1; } void Deal(int n) { int m, i, j, base, len, t; len = get_length(n); for(i = 1; i <= len; i++) //对每一位上加上一个数,例如1234 是必败,那么1235 1236 ...1239必胜,还有1244.1254.....1294,以此类推 { m = n; base = pow(10,i-1); t = (m%(base*10))/base; for(j = t; j < 9; j++){ m += base; sg[m] = true; } } m = n; base = 1; for(i = len; i < 6; i++) //后面加0开头的数 { m *= 10; for(j = 0; j < base; j++) sg[m+j] = true; base *= 10; } } void Init() { memset(sg,false,sizeof(sg)); int i; for(i=1; i<1000000; i++) //由必败点找出所有的必胜点 if(!sg[i]) Deal(i); } int main() { string s; int i,sum; Init(); while(cin>>s) { if(s[0]=='0')//0开头的都是必胜的 { printf("Yes\n"); continue; } sum=0; for(i=0; i<s.size(); i++) //字符串转变成整型 sum=sum*10+s[i]-'0'; if(sg[sum]) printf("Yes\n"); else printf("No\n"); } return 0; }