棋局评估
描述:
NPU_LL和NPU_ZTS正在玩井字棋游戏。
井字棋游戏的规则很简单:两人轮流往3*3的棋盘中放棋子,NPU_LL放的是“X”,
NPU_ZTS放的是“O”,
NPU_LL执先。当同一种棋子占据一行、一列或一条对角线的三个格子时,游戏结束,该种棋子的持有者获胜。当棋盘被填满的时候,游戏结束,双方平手。
NPU_LL设计了一种对棋局评分的方法:
-
对于NPU_LL已经获胜的局面,评估得分为(棋盘上的空格子数+1);
- 对于NPU_ZTS已经获胜的局面,评估得分为
-(棋盘上的空格子数+1);
- 对于平局的局面,评估得分为0;
-------
|X|O|X|
-------
|O|X|O|
-------
|X| | |
-------
例如上图中的局面,NPU_LL已经获胜,同时棋盘上有2个空格,所以局面得分为2+1=3。
由于NPU_LL并不喜欢计算,所以他请教擅长编程的你,如果两人都以最优策略行棋,那么当前局面的最终得分会是多少?
输入:
输入的第一行包含一个正整数T,表示数据的组数。
每组数据输入有3行,每行有3个整数,用空格分隔,分别表示棋盘每个格子的状态。0表示格子为空,1表示格子中为“X”,2表示格子中为“O”。保证不会出现其他状态。
保证输入的局面合法。(即保证输入的局面可以通过行棋到达,且保证没有双方同时获胜的情况)
保证输入的局面轮到NPU_LL行棋。
输出:
对于每组数据,输出一行一个整数,表示当前局面的得分。
输入样例:
3
1 2 1
2 1 2
0 0 0
2 1 1
0 2 1
0 0 2
0 0 0
0 0 0
0 0 0
输出样例:
3
-4
0
思路:
如果A要赢,那B在下最后一步的之前,A应该有两种方案凑够3个,这样,B只能堵住一个,A就赢了。
每次搜索前,判断一下刚走完的那个人是否会有两种方案凑够3个棋,有就可以更新答案。
注意:没走或只走了一步有可能就结束了,而之前并没有任何一方出现两种赢的方案。
一些剪枝注释在程代码里了。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 #include<cmath> 6 #include<cstdlib> 7 8 using namespace std; 9 10 int T,f,tem,iss,bla; 11 char qi[4][4]; 12 13 int judge(int te)//判断当前的人是否出现了两种可赢的方案 14 { 15 char t=te+'0'; 16 int cnt[4][4]; 17 memset(cnt,0,sizeof(cnt)); 18 for(int i=1;i<=3;i++) 19 { 20 if(qi[i][1]==qi[i][2]&&qi[i][1]==t&&qi[i][3]=='0')cnt[i][3]++; 21 if(qi[i][3]==qi[i][2]&&qi[i][2]==t&&qi[i][1]=='0')cnt[i][1]++; 22 if(qi[i][1]==qi[i][3]&&qi[i][1]==t&&qi[i][2]=='0')cnt[i][2]++; 23 } 24 for(int i=1;i<=3;i++) 25 { 26 if(qi[1][i]==qi[2][i]&&qi[2][i]==t&&qi[3][i]=='0')cnt[3][i]++; 27 if(qi[3][i]==qi[2][i]&&qi[2][i]==t&&qi[1][i]=='0')cnt[1][i]++; 28 if(qi[1][i]==qi[3][i]&&qi[1][i]==t&&qi[2][i]=='0')cnt[2][i]++; 29 } 30 if(qi[1][1]==qi[2][2]&&qi[2][2]==t&&qi[3][3]=='0')cnt[3][3]++; 31 if(qi[3][3]==qi[2][2]&&qi[2][2]==t&&qi[1][1]=='0')cnt[1][1]++; 32 if(qi[3][3]==qi[1][1]&&qi[1][1]==t&&qi[2][2]=='0')cnt[2][2]++; 33 if(qi[1][3]==qi[3][1]&&qi[3][1]==t&&qi[2][2]=='0')cnt[2][2]++; 34 if(qi[1][3]==qi[2][2]&&qi[2][2]==t&&qi[3][1]=='0')cnt[3][1]++; 35 if(qi[3][1]==qi[2][2]&&qi[2][2]==t&&qi[1][3]=='0')cnt[1][3]++; 36 int e=0; 37 for(int i=1;i<=3;i++) 38 for(int j=1;j<=3;j++) 39 { 40 // printf("%d ",cnt[i][j]); 41 if(cnt[i][j])e++; 42 } 43 return e; 44 } 45 46 int che()//判断当前棋局是否已经有三个连着的 47 { 48 for(int i=1;i<=3;i++) 49 { 50 char ch=qi[i][1]; 51 int f=1; 52 for(int j=2;j<=3;j++) 53 { 54 if(ch!=qi[i][j])f=0; 55 } 56 if(f)return ch-'0'; 57 } 58 for(int i=1;i<=3;i++) 59 { 60 char ch=qi[1][i]; 61 int f=1; 62 for(int j=2;j<=3;j++) 63 { 64 if(ch!=qi[j][i])f=0; 65 } 66 if(f)return ch-'0'; 67 } 68 if(qi[1][1]==qi[2][2]&&qi[2][2]==qi[3][3])return qi[2][2]-'0'; 69 if(qi[1][3]==qi[2][2]&&qi[2][2]==qi[3][1])return qi[2][2]-'0'; 70 return 0; 71 } 72 73 int work(int bu) 74 { 75 if(bu-iss<=1&&bu>=5)//直接赢的特殊情况:在走一步以内有5个以上的子才可能直接出现能赢的局面。 76 { 77 if(che()==1) 78 { 79 return 9-bu+1; 80 } 81 if(che()==2) 82 { 83 return -(9-bu+1); 84 } 85 } 86 if(bu>=4)//剪枝:当棋子数超过4才会出现两种能赢的方案,这时再判断。 87 { 88 int x=judge(1),y=judge(2); 89 if(y>=2&&(!(bu&1))) 90 { 91 return bu-8; 92 } 93 if(x>=2&&(bu&1)) 94 { 95 return 8-bu; 96 } 97 } 98 if(bu==7)return 0;//剪枝:如果当前已经有7颗棋还没有找到就一定是平局 99 for(int i=1;i<=3;i++)//枚举每一种走法 100 { 101 for(int j=1;j<=3;j++) 102 { 103 if(qi[i][j]=='0') 104 { 105 if(bu&1) 106 { 107 qi[i][j]='2'; 108 int tt=work(bu+1); 109 if(abs(tem)<abs(tt)) 110 { 111 tem=tt; 112 } 113 qi[i][j]='0'; 114 } 115 else 116 { 117 qi[i][j]='1'; 118 int tt=work(bu+1); 119 if(abs(tt)>abs(tem)) 120 { 121 tem=tt; 122 } 123 qi[i][j]='0'; 124 } 125 } 126 } 127 } 128 return tem; 129 } 130 131 int main() 132 { 133 scanf("%d",&T); 134 for(int I=1;I<=T;I++) 135 { 136 bla=0;iss=0;tem=0; 137 memset(qi,0,sizeof(qi)); 138 for(int i=1;i<=3;i++) 139 { 140 for(int j=1;j<=3;j++) 141 { 142 char ch=getchar(); 143 while((ch!='1')&&(ch!='2')&&(ch!='0'))ch=getchar(); 144 qi[i][j]=ch; 145 if(ch=='0')bla++; 146 else iss++; 147 } 148 } 149 if(iss<=1)//剪枝:一定是平局 150 { 151 printf("0\n"); 152 continue; 153 } 154 int ans=work(iss); 155 printf("%d\n",ans); 156 } 157 return 0; 158 }