UVa 10111 - Find the Winning Move

Find the Winning Move
http://uva.onlinejudge.org/external/101/10111.html

問題概要

4目並べで次どのような行動すれば勝てますか?
勝てる手を左上から順番にみて、一番最初のものを出力せよ。
引き分けまたは負ける場合は#####と出力せよ。
与えられる盤面はお互い2手すすめた後のものである。

解法

ゲーム理論
メモ化
二人零和有限確定完全情報ゲーム

  • 二人であり
  • 両方勝ちや負けということはなく(お互いの利益の和は常に0)
  • いつか終わる(盤面が埋め尽くされる)
  • 自分の手を決める時、すべての情報が明かされている。

状態数は3^16だけど、
ありえない状態とか、この問題の場合、
2手進んだ状態から与えられるので正確には、
3^12くらい。

#include<iostream>
#include<algorithm>
#include<map>
#include<cassert>

#define REP(i, b, n) for(int i = b; i < n; i++)
#define rep(i, n) REP(i, 0, n)
#define mp make_pair

using namespace std;
const int INF = 10000;
const int n = 4;
class State{
public:
  char t[n][n];
  bool operator<(const State &s)const{
    rep(i, n){
      rep(j, n){
	if(t[i][j] != s.t[i][j])return t[i][j] > s.t[i][j];
      }
    }
    return false;
  }
  int whoWin(){
    rep(i, n){
      bool winR = true, winC = true;
      char cR = t[i][0], cC = t[0][i];
      rep(j, n){
	if(cR != t[i][j]){
	  winR = false;
	}
	if(cC != t[j][i])winC = false;
      }
      if(winR && cR == 'o')return 1;
      if(winR && cR == 'x')return 0;
      if(winC && cC == 'o')return 1;
      if(winC && cC == 'x')return 0;
    }
    bool winT = true, winB = true;
    char cT = t[0][0], cB = t[n-1][0];
    rep(i, n){
      if(cT != t[i][i])winT = false;
      if(cB != t[n-1-i][i])winB = false;
    }
    if(winT && cT == 'o')return 1;
    if(winT && cT == 'x')return 0;
    if(winB && cB == 'o')return 1;
    if(winB && cB == 'x')return 0;
    return -1;
  }
};  

map<State, int > M[2];
int solve(State &s, int turn, bool first){
  if(!first && M[turn].find(s) != M[turn].end())return M[turn][s];
  int who = s.whoWin();
  if(who != -1){
    assert(!first);
    int ret = 0;
    if(who == turn)ret = 2;
    M[turn][s] = ret;
    return ret;
  }
  char c;
  if(turn == 0)c = 'x';
  else c = 'o';
  int ret = -1, nextTurn = (turn+1)%2;
  int maxi = 0;
  rep(i, 4){
    rep(j, 4){
      if(s.t[i][j] != '.')continue;
      s.t[i][j] = c;
      int tmp = solve(s, nextTurn, false);
      s.t[i][j] = '.';
      if(tmp == 2){
	tmp = 0;
      }
      else if(tmp == 0)tmp = 2;
      else if(tmp == 1)tmp = 1;
      ret = max(tmp, ret);
      if(ret == 2){
	if(first)cout << '(' << i << ',' << j << ')' << endl;
	break;
      }
      if(ret == 2)break;
    }
    if(ret == 2)break;
  }
  if(ret == -1)ret = 1;
  M[turn][s] = ret;
  return ret;
}

int main(){
  char c;
  while(cin >> c){
    if(c == '$')break;
    State s;
    rep(i, n){
      rep(j, n){
	cin >> s.t[i][j];
      }
    }
    if(solve(s, 0, true) != 2){
      cout << "#####" << endl;
    }
  }
}