Surreal Number模板 + hdu 6597 题解 +感悟

接上一份博弈专题 … \dots 本来想的等遇到具体题目了再看不平等博弈,结果这次多校第二场就遇到了 … \dots 因为某些蠢蠢的因素拖到现在才AC … \dots 现在想想其实比赛当时就应该能A掉 … \dots 我对不起队伍对不起队长的殷切期望嘤嘤嘤。

先给出hdu 6597 的题解以及AC代码:

题目链接

题意:
N N N 3 ∗ 3 3*3 33的棋盘,每个格子的状态有四种:放有黑子、放有白子、空着以及禁止放置。两人轮流进行操作,最后无法操作的人输。
其中,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,但像这条题目只能通过递归状态求解。(能通过递归求解的根本原因是对于这条题目来说,其状态是有限的)具体题目要根据具体情况解决问题。

最后,博弈专题正式完成!!欢呼撒花!!

你可能感兴趣的:(算法笔记)