• UVa10561


    10561 Treblecross
    Treblecross is a two player game where the goal is to get three ‘X’ in a row on a one-dimensional board.
    At the start of the game all cells in the board is empty. In each turn a player puts a ‘X’ in an empty
    cell, and if that results in there being three ‘X’ next to each other, that player wins.
    Given the current state of the game, you are to determine if the player to move can win the game
    assuming both players play perfectly. If so, you should also print all moves that will eventually lead to
    a win.
    Consider the game where the board size is 5 cells. If the first player puts a ‘X’ at position three (in
    the middle) so the state becomes ‘..X..’, he will win the game as no matter where the other player
    puts his ‘X’, the first player can get three ‘X’ in a row. If, on the other hand, the first player puts the
    ‘X’ in any other position, the second player will win the game by putting the ‘X’ in the opposite corner
    (for instance, after the second player moves the state might be ‘.X..X’). This will force the first player
    to put an ‘X’ in a position so the second player wins in the next move.
    Input
    The input begins with an integer N (N < 100), the number of states that will follow. Each state is
    represented by a string on a line by itself. The string will only contain the characters ‘.’ and ‘X’. The
    length of the string (the size of the board) will be between 3 and 200 characters, inclusive. No state
    will contain three ‘X’ in a row.
    Output
    For each case, first output ‘WINNING’ or ‘LOSING’ depending on if the player to move will win or lose the
    game. On the next line, output in increasing order all positions on the board where the player to move
    may put an X and win the game. The positions should be separated by a blank, and be in increasing
    order. The leftmost position on the board is 1.
    Sample Input
    4
    .....
    X.....X..X.............X....X..X
    .X.X...X
    ...............................................
    Sample Output
    WINNING
    3
    LOSING
    WINNING
    3
    WINNING
    1 12 15 17 20 24 28 31 33 36 47

    题意:

           有n个格子排成一行,其中有些格子里面有字符X。两个游戏者轮流操作,每次可以选择一个空格,在里面放上一个字符X。如果此时有3个连续的X出现,则该游戏者赢得比赛。初始情况下不会有3个连续的X字符出现。你的任务是判断先手必胜还是必败。如果必胜,则列举出所有的必胜策略。输入的每个字符要么是X要么是.(表示空格)。

    分析:

           如果输入中已经有XX或者X.X出现。聪明的游戏者将不会在X的旁边或者旁边的旁边放置X,这两种位置(最多4个位置)都属于不许放X的禁区。谁无法放置X谁就输了。

           于是整个游戏被X字符以及其旁的禁区分割成若干个独立的棋盘片段,每次都可以选择一个片段进行游戏,谁无法进行游戏了就算谁输。这显然是若干子游戏的和。由于每个棋盘片段都是连续的,想到利用棋盘片段的长度来表示状态。

           设g(x)表示长度为x的棋盘片段对应的游戏的SG函数值。考虑可以在左起第一或者第二或者第三或者第四或者第五或者、…个格子等位置放置一个X字符,使得当前状态发生转移,则计算SG函数的递推式应为:

    g(x) = mex{g(x - 3),g(x - 4),g(x - 5),g(1) xor g(x - 6),g(2) xor g(x - 7),…}

    边界为g(0) = 0,g(1)、g(2)、g(3)都等于0(1、2、3三种状态都只能转移到状态0)。

           在寻找必胜策略的时候就枚举所有的策略,那些后继状态的SG值为0的策略就是必胜策略。

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 using namespace std;
     5 const int MAX_N = 200;
     6 int t,res[MAX_N + 1],way,len,sg[MAX_N + 1];
     7 char str[MAX_N + 1];
     8 int judge(){
     9     for(int i = 0; i < len - 2; i++)
    10         if(str[i] == 'X' && str[i + 1] == 'X' && str[i + 2] == 'X')
    11             return true;
    12     return false;
    13 }
    14 // 使用记忆化搜索的方式求SG函数值
    15 int SG(int x){
    16     int vis[MAX_N + 1];
    17     int i,t;
    18     if(sg[x] != -1) return sg[x];
    19     if(x == 0) return sg[x] = 0; // 终态,先手必败
    20     memset(vis,false,sizeof(vis));
    21     // 枚举状态x所能达到的所有后继状态,即考虑在x格中的哪一格放置字符X
    22     for(int i = 1 ; i <= x ; i++){
    23         int t = SG(max(0,i - 3)) ^ SG(max(0,x - i - 2)); // 两个子游戏
    24         vis[t] = true;
    25     }
    26     for(int i = 0; i <= MAX_N ; i++) {
    27         if(vis[i]) continue;
    28         return sg[x] = i;
    29     }
    30 }
    31 // 当前状态是否是游戏者可行的目标状态
    32 int GetRes(){
    33     // 枚举当前的所有策略
    34     for(int i = 0 ; i < len ; i++){
    35         if(str[i] == '.'){
    36             str[i] = 'X';
    37             if(judge()){ // 当前策略得到的后继状态是已胜的
    38                 str[i] = '.';
    39                 return false; // 那么当前的状态可以得到已胜的后继状态,游戏者就要尽量规避留下这种状态给对手
    40             }
    41             str[i] = '.';
    42         }
    43     }
    44     int ans = 0,num = 0;
    45     for (int i = 0 ; i < len ; i++){
    46         // 如果遍历到了禁区
    47         if (str[i] == 'X' || (i >= 1 && str[i - 1] == 'X')
    48         || (i >= 2 && str[i - 2] == 'X')
    49         || (i + 1 < len && str[i + 1] == 'X')
    50         || (i + 2 < len && str[i + 2] == 'X')){
    51             ans ^= SG(num);
    52             num = 0;
    53         }
    54         else num++;
    55     }
    56     ans ^= SG(num);
    57     return ans == 0; // 当前状态是否是先手必败的
    58 }
    59 // 得到所有的必胜策略,即为对手留下SG函数值所有异或值为零的状态的策略
    60 void solve(){
    61     way = 0;
    62     len = strlen(str);
    63     for(int i = 0 ; i < len ; i++) {
    64         if(str[i] != '.') continue;
    65         str[i] = 'X';
    66         // 如果能够得到后继状态为已败或者必败的状态
    67         if(judge() || GetRes())
    68             res[way++] = i + 1;
    69         str[i] = '.';
    70     }
    71 }
    72 int main(){
    73     memset(sg,-1,sizeof(sg));
    74     scanf("%d",&t);
    75     while(t--){
    76         scanf("%s",str);
    77         solve();
    78         if(way == 0) printf("LOSING
    
    ");
    79         else{
    80             printf("WINNING
    %d",res[0]);
    81             for(int i = 1 ; i < way ; i++) printf(" %d",res[i]);
    82             printf("
    ");
    83         }
    84     }
    85     return 0;
    86 }
    View Code
  • 相关阅读:
    经典SQL语句大全 学者必看
    13个SQL优化技巧
    全面解析SQL SERVER 的左右内连接
    ORM框架
    JPA SQL 的复杂查询createNamedQuery
    SQL 复杂查询
    前端学习(十三)js运算符(笔记)
    前端学习(十二)js数据类型(笔记)
    前端学习(十一)函数(笔记)
    前端学习(十)初识js(笔记)
  • 原文地址:https://www.cnblogs.com/cyb123456/p/5815570.html
Copyright © 2020-2023  润新知