博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
博弈论
阅读量:5107 次
发布时间:2019-06-13

本文共 8147 字,大约阅读时间需要 27 分钟。

bzoj2463 谁能赢呢?

题目大意:给定一个n×n的方格,从(1,1)开始走,每次可以到上下左右没有到过的一个格子,alice先手,交替操作,如果先手必胜则输出'Alice’,否则输出‘Bob’。

思路:lcomyn大爷秒暴结论。后来仔细想了想,只发现走一步肯定会改变格子奇偶性。其实如果n是偶数,那么就可以用2×1的骨牌覆盖,每次走到另一端后,另一个人走到新格子,所以先手必胜;如果n是奇数,那么就可以去掉第一个格子后骨牌覆盖,胜负正好反过来。

#include
#include
using namespace std;int main(){ int n; while(scanf("%d",&n)==1) { if (n==0) break; if (n%2) printf("Bob\n"); else printf("Alice\n"); }}
View Code

 

bzoj2281 黑白棋(!!!)

题目大意:一行1*n棋盘,交替放共k个白黑棋子,两个人交替操作,先手黑,后手白,可以选1~d个棋子移动,问先手必胜的初态有几种。

思路:假设白棋子左移和黑棋子右移没有意义,所以可以把一个白黑棋子看作一对,中间的棋子数是石子数。变成了每次可以从k/2堆石子中选1~d堆任取石子(每堆取得可以不同),先手必败态是:对二进制的每一位,这一位是1的石子堆数%(d+1)都是0(nimk游戏!!!,有一堆n个石子,每次可以取1~k任意个,先手必败态是:n%(k+1)=0,因为先手取x,后手可以取k+1-x)。设fi[i][j]表示前i-1个二进制位,选了j个石子,fi[i+1][j+q*(d+1)*(1<<i)]+=fi[i][j]*c(k/2,q*(d+1))。

答案是总-先手必败=c(n,k)-fi[up][i]*c(n-i-k/2,k/2)(对于有i个石子的方案,从n-i个位置中选出k对两两相临)

因为题目中没有之前的假设,所以可能会导致从必败态->必败态,这种转化就是错误的。

#include
#include
#include
#include
#define N 10005#define M 105#define up 15#define p 1000000007LL#define LL long longusing namespace std;LL fi[up+1][N],fac[N],inv[N];LL mi(LL x,int y){ LL a=1LL; for (;y;y>>=1){ if (y&1) a=a*x%p; x=x*x%p; }return a;}LL getc(int n,int m){ if (n
=0;--i) inv[i]=inv[i+1]*(LL)(i+1)%p; for (fi[0][0]=1LL,i=0;i
>1);++q) add(fi[i+1][j+a*(1<
>1,a)%p); } for (ans=getc(n,k),i=0;i<=n-k;++i) add(ans,-(fi[up][i]*getc(n-i-(k>>1),k>>1)%p)); printf("%I64d\n",ans);}
View Code

 

sg函数

一个状态的sg值是它所有后继状态sg值得mex(最小未出现的自然数),sg=0是必败状态。大多情况下,多个子问题的sg的nim和!=0就是总问题可以必胜。

bzoj1228 E&D

题目大意:给定n堆石子,两堆一组,每次可以从一组中扔掉一堆,将另一堆分为两堆(每堆个数>=1),问先手有无必胜策略。

思路:sg函数在很多情况下可以通过打表找规律,然后求nim和判断。这道题目中的sg函数很有规律,斜着看是一个个的三角形,并且有递归的感觉,所以可以从2^30往下递归一下,相应的减去2^x就可以了,最后如果xy都是1,就是0。

#include
#include
#include
#include
#define LL long longusing namespace std;int sg(int x,int y){ int i=1<<30,j=30,ans=31; for (;j;--j,i>>=1){ if (x<=i&&y<=i) ans=j; else{ if (x>i) x-=i; if (y>i) y-=i; } }if (x==1&&y==1) return 0; return ans;}int main(){ int t,i,j,n,u,v,cnt; scanf("%d",&t); while(t--){ scanf("%d",&n);n/=2;cnt=0; for (i=1;i<=n;++i){ scanf("%d%d",&u,&v); cnt^=sg(u,v); }if (cnt) printf("YES\n"); else printf("NO\n"); }}
View Code

 

bzoj3576 江南乐

题目大意:给定n堆石子,两个人轮流操作,操作是:挑一堆不小于f个的,分成不小于2的任意堆,且要求尽量均分。不能操作的人输。

思路:考虑求sg[i],可以循环j从2~i表示分的堆数,k=i/j,所以i/j+1的有k1=i%j个,i/j的有k2=j-i%j个,这些sg值的异或就是答案(因为两个相同数的异或是0,所以只需要i%j^1=1才会^sg[i/j+1],sg[i/j]同理)。但因为i/j的取值只有根i个,且只需要考虑k1、k2的奇偶就可以了,所以可以做到n根n的复杂度。写成记忆化搜索会快很多。

#include
#include
#include
#include
#include
#define N 100001 using namespace std; int sg[N]={
0},vi[N]={
0},f; void pre(){ int i,j,k,k1,k2,la; for (i=f;i
View Code

 

二分图博弈

bzoj2437 兔兔与蛋蛋

题目大意:给定一张棋盘,有白子黑子和空格子,兔兔先手,选一个白格子移到空格子;蛋蛋后手,选一个黑格子移到空格子。给出一个操作序列,问兔兔那几个操作是失误的(认为是失误的当且仅当兔兔操作前有必胜策略,兔兔操作后也有必胜策略)。

思路:黑白染色,相当于空格子走一些黑白交替的无环的路径,不能操作的人输。把空格子和黑格子看作一样,对那些本身颜色和黑白染色一样的格子编号(只有这些格子可能移动)。如果一个点一定在二分图最大匹配中,认为这个点有必胜策略。所以这道题目中要判断每次兔兔操作前后是否有必胜策略,如果都有,就认为它失误了。动态的二分图匹配。

#include
#include
#include
#include
#define N 45#define M 10000using namespace std;int mp[N][N],mt[M]={
0},vi[M]={
0},tot,point[M]={
0},next[M],en[M],fb[M]={
0},mi[M], id[N][N]={
0},dx[4]={
1,0,-1,0},dy[4]={
0,1,0,-1};int in(){ char ch=getchar(); while(ch!='.'&&ch!='O'&&ch!='X') ch=getchar(); if (ch=='.') return -1; return ch=='X';}int ab(int x){
return (x<0 ? -x : x);}void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;}bool find(int u){ int i,v; for (i=point[u];i;i=next[i]){ if (vi[v=en[i]]==tot||fb[v]) continue; vi[v]=tot; if (!mt[v]||find(mt[v])){ mt[v]=u;mt[u]=v;return true; } }return false;}int main(){ int n,m,i,j,k,q,x,y,bx,by,nm=0;scanf("%d%d",&n,&m); for (i=1;i<=n;++i) for (j=1;j<=m;++j) if ((mp[i][j]=in())<0) mp[bx=i][by=j]=1; for (i=1;i<=n;++i) for (j=1;j<=m;++j) if ((mp[i][j]&&(ab(i-bx)+ab(j-by))%2==0)||(!mp[i][j]&&(ab(i-bx)+ab(j-by))%2==1)) id[i][j]=++nm; for (tot=0,i=1;i<=n;++i) for (j=1;j<=m;++j) if (id[i][j]) for (k=0;k<4;++k) if (id[x=i+dx[k]][y=j+dy[k]]) add(id[i][j],id[x][y]); for (tot=0,i=1;i<=nm;++i) if (!mt[i]){++tot;find(i);} scanf("%d",&q);q<<=1; for (i=1;i<=q;++i){ if (mt[id[bx][by]]){ j=mt[id[bx][by]]; mt[j]=mt[id[bx][by]]=0; fb[id[bx][by]]=1; ++tot;if(find(j)) mi[i]=1; }else{fb[id[bx][by]]=1;mi[i]=1;} scanf("%d%d",&bx,&by); }for (nm=0,i=1;i<=q;i+=2) if (!mi[i]&&!mi[i+1]) ++nm; printf("%d\n",nm); for (i=1;i<=q;i+=2) if (!mi[i]&&!mi[i+1]) printf("%d\n",(i+1)/2);}
View Code

 

bzoj1443 游戏

题目大意:给定一个网格,有些格子不能走,有一枚棋子,每次走到相邻的格子。问选那些起始位置能使后手赢。

思路:黑白染色,如果一个点不一定在最大匹配上,就是后手赢。所以在匹配每个点是不是一定在就可以了。

#include
#include
#include
#include
#define N 105#define M 40005using namespace std;int id[N][N],tot=0,point[M]={
0},next[M],en[M],mt[2][M]={
0},vi[M]={
0},fb[M]={
0},mp[N][N],tt=0, dx[4]={
0,1,0,-1},dy[4]={
1,0,-1,0};int in(){ char ch=getchar(); while(ch!='.'&&ch!='#') ch=getchar(); return ch=='.';}void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;}bool find(int k,int u){ int i,v; for (i=point[u];i;i=next[i]){ if (vi[v=en[i]]==tt||fb[v]) continue; vi[v]=tt; if (!mt[k][v]||find(k,mt[k][v])){ mt[k][v]=u;mt[k][u]=v;return true; } }return false;}int main(){ int n,m,i,j,k,x,y,cnt=0,nm=0;scanf("%d%d",&n,&m); for (i=1;i<=n;++i) for (j=1;j<=m;++j) if ((mp[i][j]=in())>0) id[i][j]=++nm; for (tot=0,i=1;i<=n;++i) for (j=1;j<=m;++j) if (id[i][j]) for (k=0;k<4;++k) if (id[x=i+dx[k]][y=j+dy[k]]) add(id[i][j],id[x][y]); for (i=1;i<=nm;++i) if (!mt[0][i]){++tt;find(0,i);} for (i=1;i<=n;++i) for (j=1;j<=m;++j){ if (!id[i][j]) continue; if (!mt[0][id[i][j]]){ if (!cnt) printf("WIN\n"); printf("%d %d\n",i,j); ++cnt;continue; }else{ for (k=1;k<=nm;++k) mt[1][k]=mt[0][k]; fb[id[i][j]]=1;k=mt[1][id[i][j]]; ++tt;mt[1][k]=mt[1][id[i][j]]=0; if (find(1,k)){ if (!cnt) printf("WIN\n"); printf("%d %d\n",i,j);++cnt; }fb[id[i][j]]=0; } }if (!cnt) printf("LOSE\n");}
View Code

还有一种写法比较快,用网络流求出最大匹配之后,如果从S通过有流量的边到的左边的点和从T通过没流量的边到的右边的点都是不一定出现的点,就是答案。

 

bzoj4600 硬币游戏(!!!

题目大意:给出一行n个硬币和mq,每次可以选一个反面向上的硬币c*2^i*3^j,(1)选定p、q,p>=1,q>=1&&p*q<=i&&q<=mq,可以把c*2^(i-k*q)*3^j翻过来;(2)选定p、q,p>=1,q>=1&&p*q<=j&&q<=mq,可以把c*2^i*3^(j-k*q)翻过来。

思路:可以看出和c没关系,可以对c的不同看作不同的游戏,sg异或起来就可以了。除去c之后,可以看作i*j的矩阵,每次可以翻i行或者j列的等差数列的棋子,可以把n个棋子看作x个只有右下角(i,j)是1的独立游戏的sg异或,因为每个棋子如果被翻偶数次是不影响先后手胜负的,对于每一个独立游戏,除了右下角的棋子翻了奇数次,其他都翻了偶数次,所以看作独立的是对的。枚举i、j、p、q看作是i、j的一种后继状态,后继又可以看作一个游戏,sg异或起来,求出mex就是ij的sg了。

(orz现场a掉的vampire爷)

#include
#include
#include
#include
#define N 15#define M 21#define up 30001using namespace std;int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;}int sg[M][N][N],m2[N],m3[N],ai[up][2],vi[500]={
0},vt=0;void pre(int mq){ int i,j,k,p,q,ci,mx; for (i=0;i
>=1) ++ai[i][0]; for (j=i;j%3==0;j/=3) ++ai[i][1]; }for (i=1;i
View Code

 

转载于:https://www.cnblogs.com/Rivendell/p/4765414.html

你可能感兴趣的文章
autopep8
查看>>
GIT在Linux上的安装和使用简介
查看>>
java 类型转型
查看>>
基于C#编程语言的Mysql常用操作
查看>>
【转】Java反射 之 反射基础
查看>>
mysql数据库备份和还原的常用命令
查看>>
s3c2440实验---定时器
查看>>
HBase配置性能调优(转)
查看>>
MyEclipse10安装SVN插件
查看>>
[转]: 视图和表的区别和联系
查看>>
Regular Experssion
查看>>
python中的字符编码
查看>>
图论例题1——NOIP2015信息传递
查看>>
uCOS-II中的任务切换-图解多种任务调度时机与问题
查看>>
CocoaPods的安装和使用那些事(Xcode 7.2,iOS 9.2,Swift)
查看>>
Android 官方新手指导教程
查看>>
幸运转盘v1.0 【附视频】我的Android原创处女作,请支持!
查看>>
UseIIS
查看>>
为什么int型最大的数是2147483647
查看>>
数据库连接的三层架构
查看>>