接上一份博弈专题 … \dots …本来想的等遇到具体题目了再看不平等博弈,结果这次多校第二场就遇到了 … \dots …因为某些蠢蠢的因素拖到现在才AC … \dots …现在想想其实比赛当时就应该能A掉 … \dots …我对不起队伍对不起队长的殷切期望嘤嘤嘤。
先给出hdu 6597 的题解以及AC代码:
题目链接
题意:
有 N N N个 3 ∗ 3 3*3 3∗3的棋盘,每个格子的状态有四种:放有黑子、放有白子、空着以及禁止放置。两人轮流进行操作,最后无法操作的人输。
其中,Alice的操作有以下三种:
(1) 选择一个白棋子所在格子以及其上下的两个格子,将他们的状态转变为禁止放置。(2) 选择一个白棋子所在格子以及其左右的两个格子,将他们的状态转变为禁止放置。
(3) 选择一个白棋子所在格子以及其上下左右的四个格子,将他们的状态转变为禁止放置。
当然,假如这个白棋子所在格子的上(下左右)没有格子,则忽略将其转为禁止放置的操作。
Bob的操作只有一种:选择一个白棋子所在格子,将他的状态转变为禁止放置。
游戏的可能结果有:
(1) 无论先后手,Alice必胜。
(2) 无论先后手,Bob必胜。
(3) 先手必胜。
(4) 后手必胜。
(5) 无法判断。
对于每个样例,输出游戏的结果。
题解:
显然本题考察的是不平等博弈。要用到 S u r r e a l Surreal Surreal N u m b e r Number Number的相关知识。
这里不再赘述,附上一份讲解的很详细的链接:Surreal Number应对不平等博弈
显然,对于一个格子,空着与无法放置的状态是等价的。于是,对于每一个棋盘,我们都能用九个{O,X,# }来描述其状态。于是,一共有 3 9 3^9 39 种棋盘的状态。我们很容易想到题目的大体思路:存储每一个状态的 S u r r e a l Surreal Surreal N u m b e r Number Number 。并由 S u r r e a l Surreal Surreal N u m b e r Number Number 的性质得,Alice必胜当且仅当所有棋盘的 S u r r e a l Surreal Surreal N u m b e r Number Number 和大于 0 0 0 。Bob必胜当且仅当所有棋盘的 S u r r e a l Surreal Surreal N u m b e r Number Number 和小于 0 0 0 。当 S u r r e a l Surreal Surreal N u m b e r Number Number 和等于 0 0 0 时,后手必胜。
记 S ( s t a t e ) = S u r r e a l S(state)=Surreal S(state)=Surreal N u m b e r ( s t a t e ) Number(state) Number(state)。
首先, 对应 S u r r e a l Surreal Surreal N u m b e r Number Number 中的空集 { ∣ } \{ | \} {∣},当棋盘全为 # 时, S = 0 S=0 S=0。由次状态进行递归,求解每个状态的 S S S。
自己做的时候掉进了一个坑:对于一个只有{O,#}的棋盘,我设它的 S = S= S=其中O的数量。但其实并不是,比如一个全是O的棋盘,其 S S S值为8。(另外在求解 S u r r e a l Surreal Surreal N u m b e r Number Number 的过程中,其实只需要设置初始空集就行了,这个错误完全是自己作。)
AC代码:
#include
const int inf=1000;
const int maxn=3e5;
using namespace std;
struct fs{ //分数结构体
int fz,fm;
fs(int _fz=0,int _fm=1):fz(_fz),fm(_fm){}
bool operator==(const fs &oth)const{
return fz*oth.fm==fm*oth.fz;
}
friend int ceil(const fs &x){
return (int)ceil(1.0*x.fz/x.fm);
}
friend int floor(const fs &x){
return (int)floor(1.0*x.fz/x.fm);
}
friend fs abs(const fs &x){
return fs(abs(x.fz),abs(x.fm));
}
bool operator<(const fs &oth)const{
return (double)fz/fm<(double)oth.fz/oth.fm;
//return fz*oth.fm
}
fs operator+(const fs &oth)const{
int n_fm=fm*oth.fm;
int n_fz=fz*oth.fm+fm*oth.fz;
int yf=__gcd(n_fz,n_fm);
return fs(n_fz/yf,n_fm/yf);
}
friend void print(const fs &x){
printf("%d/%d\n",x.fz,x.fm);
}
};
int pd(int dex,int n){ //判断二进制状压后的第n位上的棋子类型
if(dex<(1<<(2*n-2)))return 2; //二进制的右二位、最右位表示第一枚棋子,以此类推
int flag1,flag2;
dex>>=(2*n)-2;
flag1=dex&1;
if(flag1)return 0; //11空格
dex>>=1;
flag2=dex&1;
if(flag2)return 1; // 10黑色
else return 2; // 00白色
}
void solve(int x){ //逆向解出棋盘状态
char s[7][7];
for(int i=1;i<=3;i++){
if(x&1)s[i][0]='#';
else if((x>>1)&1)s[i][0]='X';
else s[i][0]='O';
s[i][1]='|';
x>>=2;
if(x&1)s[i][2]='#';
else if((x>>1)&1)s[i][2]='X';
else s[i][2]='O';
s[i][3]='|';
x>>=2;
if(x&1)s[i][4]='#';
else if((x>>1)&1)s[i][4]='X';
else s[i][4]='O';
s[i][5]=0;
x>>=2;
}
for(int i=1;i<=3;i++)
printf("%s\n",s[i]);
}
int sw(int dex,int n,int typ){ // 换第i个石头变为空格
int tmp=dex;
tmp|=3<<(2*n-2);
if(typ==1 || typ==3){ //变换左右
if(n%3!=0){
tmp|=3<<(2*(n+1)-2);
}
if(n%3!=1){
tmp|=3<<(2*(n-1)-2);
}
}
if(typ==2 || typ==3){ //变换上下
if((n+2)/3!=1){
tmp|=3<<(2*(n-3)-2);
}
if((n+2)/3!=3){
tmp|=3<<(2*(n+3)-2);
}
}
return tmp;
}
fs a[maxn];
void init(){ //初始化,将数组中每一个值都赋inf+2
for(int i=0;i<maxn;i++){
a[i].fm=1;
a[i].fz=inf+2;
}
}
fs getl(int dex); //获取左集合的S
fs getr(int dex); //获取右集合的S
fs getsr(int dex); //获取{ | }整体的S
fs calc(fs l,fs r){ //get辅助函数 Surreal Number计算规则
if (r<l) swap(l,r);
int x=ceil(l);
if (l==x) ++x;
if (fs(x)<r){
if (fs(0)<l||l==0||l<0&&fs(0)<r) return x;
int y=floor(r);
if (fs(y)==r) --y;
if (r<0||r==0) return y;
return y;
}
for (int y=1;;y*=2){
for (x=1;;++x){
fs tmp1=fs(x,y);
fs tmp2=fs(-x,y);
if (l<tmp1&&tmp1<r) return tmp1;
if (l<tmp2&&tmp2<r) return tmp2;
if (fs(0)<r&&r<tmp1) break;
if (l<0&&tmp2<l) break;
}
}
return fs(0);
}
fs get(fs l,fs r){ //计算S
fs res=fs(0);
if(l.fz==-inf-1 && r.fz==inf+1)return 0; //当左集合和右集合都是空,输出0
else if(l.fz==-inf-1){ //当左集合为空,输出S(右集合)-1
res=res+r+fs(-1,1);
}
else if(r.fz==inf+1){ //当右集合为空,输出S(左集合)+1
res=res+l+fs(1,1);
}
else{
res=calc(l,r); //调用calc计算
}
return res;
}
fs getl(int dex){ //左集合最大值
fs res=-inf-1; //若左集合为空,输出-inf-1
for(int i=1;i<=9;i++){
if(pd(dex,i)==2){
res=max(res,getsr(sw(dex,i,1))); //Alice三种选择,取最大值
res=max(res,getsr(sw(dex,i,2)));
res=max(res,getsr(sw(dex,i,3)));
}
}
return res;
}
fs getr(int dex){ //右集合最小值
fs res=inf+1; //若左集合为空,输出inf+1
for(int i=1;i<=9;i++){
if(pd(dex,i)==1){
res=min(res,getsr(sw(dex,i,0)));
}
}
return res;
}
fs getsr(int dex){ //获得某个状态的sunr
if(a[dex].fz!=inf+2){ //如果已经保存过,则直接取用,如果没有则计算。
return a[dex];
}
else{
fs m=getl(dex),n=getr(dex);
a[dex]=get(m,n);
//printf("getl,getr=\n");
//print(m);
//print(n);
//print(a[dex]);
return a[dex];
}
}
int make(){ //构造映射,将棋盘状态转化为二进制数
int res=0;
char s[10];
int dex=1;
for(int i=1;i<=3;i++){
scanf("%s",s);
for(int j=1;j<=3;j++){
int tmp=0;
if(s[2*j-2]=='X')tmp=2;
else if(s[2*j-2]=='.'||s[2*j-2]=='#')tmp=3;
res+=(tmp<<(2*dex-2));
dex++;
}
}
return res;
}
int main(){
init();
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int ca;
scanf("%d",&ca);
while(ca--){
int n;
fs res=fs(0);
scanf("%d",&n);
for(int i=0;i<n;i++){
int dex=make();
res=res+getsr(dex);
}
if(fs(0)<res)printf("Alice\n");
else if(res<fs(0))printf("Bob\n");
else printf("Second\n");
}
return 0;
}
下面给出总结的 S u r r e a l Surreal Surreal N u m b e r Number Number 模板:
#include
const int inf=1000;
const int maxn=3e5;
using namespace std;
struct fs{ //分数结构体
int fz,fm;
fs(int _fz=0,int _fm=1):fz(_fz),fm(_fm){}
bool operator==(const fs &oth)const{
return fz*oth.fm==fm*oth.fz;
}
friend int ceil(const fs &x){
return (int)ceil(1.0*x.fz/x.fm);
}
friend int floor(const fs &x){
return (int)floor(1.0*x.fz/x.fm);
}
friend fs abs(const fs &x){
return fs(abs(x.fz),abs(x.fm));
}
bool operator<(const fs &oth)const{
return (double)fz/fm<(double)oth.fz/oth.fm;
}
fs operator+(const fs &oth)const{
int n_fm=fm*oth.fm;
int n_fz=fz*oth.fm+fm*oth.fz;
int yf=__gcd(n_fz,n_fm);
return fs(n_fz/yf,n_fm/yf);
}
friend void print(const fs &x){
printf("%d/%d\n",x.fz,x.fm);
}
};
fs a[maxn];
void init(){ //初始化,将数组中每一个值都赋inf+2
for(int i=0;i<maxn;i++){
a[i].fm=1;
a[i].fz=inf+2;
}
}
fs getsr(int dex); //获取dex状态的S
fs getl(int dex); //获取左集合的S
fs getr(int dex); //获取右集合的S
fs get(fs l,fs r); //已知S(l)和S(r)得到S({l|r})
fs calc(fs l,fs r){ //get辅助函数 (Surreal Number计算规则)
if (r<l) swap(l,r);
int x=ceil(l);
if (l==x) ++x;
if (fs(x)<r){
if (fs(0)<l||l==0||l<0&&fs(0)<r) return x;
int y=floor(r);
if (fs(y)==r) --y;
if (r<0||r==0) return y;
return y;
}
for (int y=1;;y*=2){
for (x=1;;++x){
fs tmp1=fs(x,y);
fs tmp2=fs(-x,y);
if (l<tmp1&&tmp1<r) return tmp1;
if (l<tmp2&&tmp2<r) return tmp2;
if (fs(0)<r&&r<tmp1) break;
if (l<0&&tmp2<l) break;
}
}
return fs(0);
}
fs get(fs l,fs r){ //计算S
fs res=fs(0);
if(l.fz==-inf-1 && r.fz==inf+1)return 0; //当左集合和右集合都是空,输出0
else if(l.fz==-inf-1){ //当左集合为空,输出S(右集合)-1
res=res+r+fs(-1,1);
}
else if(r.fz==inf+1){ //当右集合为空,输出S(左集合)+1
res=res+l+fs(1,1);
}
else{
res=calc(l,r); //调用calc计算
}
return res;
}
fs getsr(int dex){ //获得某个状态的sunr
if(a[dex].fz!=inf+2){ //如果已经保存过,则直接取用,如果没有则计算。
return a[dex];
}
else{
fs m=getl(dex),n=getr(dex);
a[dex]=get(m,n);
return a[dex];
}
}
有的题目是能直接根据情况求出其对应的S,但像这条题目只能通过递归状态求解。(能通过递归求解的根本原因是对于这条题目来说,其状态是有限的)具体题目要根据具体情况解决问题。
最后,博弈专题正式完成!!欢呼撒花!!