HackerRank Week of Code - 24 参加日記
392位/9177人。難易度Hardの「XOR Matrix」が解けそうで解けなく3日ほど苦しんだ(結果は部分正解)。Easy問題は省略する。
盤の大きさが4x4、かつクイーン・ルーク・ナイト・ビショップだけが存在するミニチェスを考える。勝利条件は相手のクイーンを取ることとする。盤面g個が与えられたとき、m手以内に先手が必勝かどうかをそれぞれ答えよ。
先手後手それぞれ、クイーンが1個、ルークが0~2個、ナイトとビショップが合計0~2個ある
1 <= g <= 200
1 <= m <= 6
十分に小さいゲーム木なので、MinMax法で簡単に求められる。ただし実装が少し重い。ちなみに、所定の手数までの勝敗なので、Zobrist Hashなどで同一局面を排除するとWAになる。
class Program { enum PIECES { EMPTY, Q, N, B, R, q, n, b, r }; static void Main(string[] args) { var g = Int32.Parse(Console.ReadLine()); while( g > 0 ) { var str = Console.ReadLine().Split(); var w = Int32.Parse(str[0]); var b = Int32.Parse(str[1]); var m = Int32.Parse(str[2]); var board = GetInitBoard(); while ( w > 0 ) { SetPiece_Init(true, board, Console.ReadLine().Split()); w--; } while ( b > 0 ) { SetPiece_Init(false, board, Console.ReadLine().Split()); b--; } if (MinMax('W', board, m)) Console.WriteLine("YES"); else Console.WriteLine("NO"); g--; } } static PIECES[,] GetInitBoard() { var ret = new PIECES[4, 4]; for (int row = 0; row < 4; row++) { for (int col = 0; col < 4; col++) { ret[row, col] = PIECES.EMPTY; } } return ret; } static void SetPiece_Init(bool isWhite, PIECES[,] board, string[] inStrs) { var piece = GetPiece(!isWhite ? inStrs[0].ToLower()[0] : inStrs[0][0]); var col = inStrs[1][0] - 'A'; var row = inStrs[2][0] - '1'; MovePiece(board, -1, -1, row, col, piece).Item2; } static PIECES GetPiece(char p) { switch (p) { case 'Q': return PIECES.Q; case 'N': return PIECES.N; case 'B': return PIECES.B; case 'R': return PIECES.R; case 'q': return PIECES.q; case 'n': return PIECES.n; case 'b': return PIECES.b; case 'r': return PIECES.r; } return PIECES.EMPTY; } //return IsTerminalState static bool MovePiece(PIECES[,] board, int prevRow, int prevCol, int row, int col, PIECES piece) { var isTerminal = false; if (prevRow != -1) board[prevRow, prevCol] = PIECES.EMPTY; if (board[row, col] != PIECES.EMPTY) { if (board[row, col] == PIECES.Q || board[row, col] == PIECES.q) isTerminal = true; } board[row, col] = piece; return isTerminal; } static char GetNextTurn(char turn) { return turn == 'W' ? 'B' : 'W'; } static bool IsHisPiece(char turn, PIECES piece) { if(piece == PIECES.EMPTY) return false; if (turn == 'W') return piece == PIECES.Q || piece == PIECES.N || piece == PIECES.B || piece == PIECES.R; if (turn == 'B') return piece == PIECES.q || piece == PIECES.n || piece == PIECES.b || piece == PIECES.r; return false; } static bool Win(PIECES[,] board, char turn) { bool Q_exist = false; bool q_exist = false; for (int row = 0; row < 4; row++) { for (int col = 0; col < 4; col++) { if(board[row, col] == PIECES.Q) Q_exist = true; if(board[row, col] == PIECES.q) q_exist = true; } } if(turn == 'W' && !q_exist) return true; if(turn == 'B' && !Q_exist) return true; return false; } static PIECES[,] CopyBoard(PIECES[,] org) { var ret = new PIECES[4, 4]; for (int row = 0; row < 4; row++) { for (int col = 0; col < 4; col++) { ret[row, col] = org[row, col]; } } return ret; } static bool IsInRange(int row, int col) { return IsInRange(row) && IsInRange(col); } static bool IsInRange(int pos) { return pos >= 0 && pos < 4; } static int[,] Moves_N = new int[,] { { 2, 1}, { 1, 2}, { -1, 2}, { -2, 1}, { -2, -1}, { -1, -2}, { 1, -2}, { 2, -1}, }; static bool IsEnemy(PIECES p, PIECES p2) { if (p == PIECES.Q || p == PIECES.R || p == PIECES.B || p == PIECES.N) return p2 == PIECES.q || p2 == PIECES.r || p2 == PIECES.b || p2 == PIECES.n; if (p == PIECES.q || p == PIECES.r || p == PIECES.b || p == PIECES.n) return p2 == PIECES.Q || p2 == PIECES.R || p2 == PIECES.B || p2 == PIECES.N; return false; } static void TryMove(ref List<Tuple<int, int>> moves, PIECES[,] board, int row, int col, PIECES piece, int rowDiff, int colDiff) { row += rowDiff; col += colDiff; while (IsInRange(row, col)) { if (board[row, col] == PIECES.EMPTY) { moves.Add(new Tuple<int, int>(row, col)); } else if(IsEnemy(piece, board[row, col])) { moves.Add(new Tuple<int, int>(row, col)); break; } else { break; } row += rowDiff; col += colDiff; } } static List<Tuple<int, int>> GetNextPositions(int row, int col, PIECES piece, PIECES[,] board) { var ret = new List<Tuple<int, int>>(); if (piece == PIECES.N || piece == PIECES.n) { for (int i = 0; i < Moves_N.GetLength(0); i++) { var nextRow = row + Moves_N[i, 0]; var nextCol = col + Moves_N[i, 1]; if (IsInRange(nextRow, nextCol)) { if(board[nextRow, nextCol] == PIECES.EMPTY || IsEnemy(piece, board[nextRow, nextCol])) ret.Add(new Tuple<int, int>(nextRow, nextCol)); } } } else { switch (piece) { case PIECES.Q: case PIECES.q: TryMove(ref ret, board, row, col, piece, 1, 0); TryMove(ref ret, board, row, col, piece, 0, 1); TryMove(ref ret, board, row, col, piece, -1, 0); TryMove(ref ret, board, row, col, piece, 0, -1); TryMove(ref ret, board, row, col, piece, 1, 1); TryMove(ref ret, board, row, col, piece, -1, 1); TryMove(ref ret, board, row, col, piece, 1, -1); TryMove(ref ret, board, row, col, piece, -1, -1); break; case PIECES.R: case PIECES.r: TryMove(ref ret, board, row, col, piece, 1, 0); TryMove(ref ret, board, row, col, piece, 0, 1); TryMove(ref ret, board, row, col, piece, -1, 0); TryMove(ref ret, board, row, col, piece, 0, -1); break; case PIECES.B: case PIECES.b: TryMove(ref ret, board, row, col, piece, 1, 1); TryMove(ref ret, board, row, col, piece, -1, 1); TryMove(ref ret, board, row, col, piece, 1, -1); TryMove(ref ret, board, row, col, piece, -1, -1); break; } } return ret; } //turn: 'W' or 'B' //return true if the player won static bool MinMax(char turn, PIECES[,] board, int depth) { //White could not capture black's Qween within m moves if (depth == 0) return turn == 'W' ? false : true; var nextTurn = GetNextTurn(turn); for (int row = 0; row < 4; row++) { for (int col = 0; col < 4; col++) { if (IsHisPiece(turn, board[row, col])) { foreach (var nextPos in GetNextPositions(row, col, board[row, col], board)) { var nextBoard = CopyBoard(board); var nextIsTerm = MovePiece(nextBoard, row, col, nextPos.Item1, nextPos.Item2, board[row, col]); if (nextIsTerm) { if (Win(nextBoard, turn)) return true; } else { if (!MinMax(nextTurn, nextBoard, depth - 1)) return true; } } } } } return false; } }
数列a[0], a[1]... a[n]が与えられる。次の操作をm-1回行ったときの最終的な値を出力せよ。
- i < n-1 ならa[i]へ XOR(a[i], a[i+1])を代入
- i = n-1 ならa[i]へ XOR(a[i], a[0])を代入
1 <= n <= 10^5
0 <= m <= 10^18 - 1
0 <= a[j] <= 10^9
たとえば要素0にXOR加算する要素は、
1
1 1
1 0 1
1 1 1 1
1 0 0 0 1
1 1 0 0 1 1
1 0 1 0 1 0 1
1 1 1 1 1 1 1 1
1 0 0 0 0 0 0 0 1
のようにパスカルのフラクタルと同じ形になる。
(最上段は初期状態、その後は上から、操作1終了後、操作2終了後・・・の順)
パスカルのフラクタルがすべて1になるのは2^k回目のときで、その次の状態が該当要素と最終要素のXORになる。よって、現在の状態から2^kだけ飛ばして計算することが可能。
要素0以外も、シフトするだけで同じパターンとなる。
static void Main(string[] args) { var str = Console.ReadLine().Split(); var n = Int32.Parse(str[0]); var m = long.Parse(str[1]); var a = ReadLine().Split().Select(s => long.Parse(s)).ToArray(); m--; while (m > 0) { long cnt = 1; while (cnt << 1 <= m) { cnt <<= 1; } var tmp = new long[a.Length]; for (long i = 0; i < a.Length; i++) { tmp[i] = a[i] ^ a[(i + cnt) % a.Length]; } a = tmp; m -= cnt; } foreach (var v in a) { Console.Write(v.ToString() + " "); } Console.WriteLine(); }
Shashank and the Palindromic Strings
文字列のリストがq個与えられる。それぞれについて次の条件を満たすパターン数を答えよ。
- を の空文字以外のサブシーケンスとしたとき、 が回文になる
1 <= q <= 50
1 <= n <= 50
全文字列を連結した文字列sを考える。DPを次のように定義する。
:セグメント[r, c]で回文になるサブシーケンスのパターン数。
was1: s[r]の元文字列にある文字を含むかどうか
was2: s[c]の元文字列にある文字を含むかどうか
更新条件は2通りある。
1. s[r]とs[c]の元文字列が同じとき
// r・cの元文字列を使わないのは空文字のパターンのみ dp[r, c, 0, 0] = 1; // s[r]とs[l]を回文として使わないパターン dp[r, c, 1, 1] = dp[r, c - 1, 1, 1] + dp[r + 1, c, 1, 1]; dp[r, c, 1, 1] -= dp[r + 1, c - 1, 1, 1]; // 重複を排除 // s[r]とs[l]を回文として使うパターン if (s[r] == s[c]) { dp[r, c, 1, 1] += dp[r + 1, c - 1, 1, 1]; dp[r, c, 1, 1] += dp[r + 1, c - 1, 0, 0]; }
2. s[r]とs[c]の元文字列が違うとき
2-1. s[r+1]とs[r]の元文字列が違い、さらにs[c-1]とs[c]の元文字列が違う場合
// rが元文字列の右端で、cが元文字列の左端のとき // どちらの文字列も使わない場合は、その内側の結果と同じ dp[r, c, 0, 0] = (r + 1 == c ? 1 : dp[r + 1, c - 1, 1, 1]); // どちらかの文字列を使う場合は、使わない側をはぶいたパターン数に一致 dp[r, c, 1, 0] = dp[r, c - 1, 1, 1]; dp[r, c, 0, 1] = dp[r + 1, c, 1, 1]; // どちらも使うとき。s[r]とs[c]が一致しないと0になる dp[r, c, 1, 1] = (s[r] == s[c] ? dp[r, c, 0, 0] : 0);
2-2. s[r+1]とs[r]の元文字列が違うが、s[c-1]とs[c]の元文字列は同じ場合
// s[r]が元文字列の右端だがs[c]は元文字列の左端ではないとき dp[r, c, 0, 0] = dp[r, c - 1, 0, 0]; //s[r]、s[c]いずれの元文字列も使わない //dp[r + 1, c - 1, 0, 0]のような気がするが・・・? dp[r, c, 1, 0] = dp[r, c - 1, 1, 0]; //s[r]の元文字列を使う dp[r, c, 0, 1] = dp[r + 1, c, 1, 1]; //s[r]の元文字列を使わない dp[r, c, 1, 1] = dp[r, c - 1, 1, 1]; //どっちも使う->s[r]を使うしかない if (s[r] == s[c]) { //s[r]とs[c]が同じなら、この2つを回文に使うパターンが追加される dp[r, c, 1, 1] += dp[r + 1, c - 1, 1, 1]; dp[r, c, 1, 1] += dp[r + 1, c - 1, 1, 0]; //元文字列が隣り合っていた場合! if (A[r] + 1 == A[c]) { dp[r, c, 1, 1]++; // 1増えるのみ } }
2-3. s[r+1]とs[r]の元文字列が同じだが、s[c-1]とs[c]の元文字列は違う場合
(2-2と同様)
2-4. s[r+1]とs[r]の元文字列も、s[c-1]とs[c]の元文字列も違う場合
// s[r]が元文字列の右端ではなく、s[c]が元文字列の左端ではないとき // どちらの元文字列も使わないときは1つ内側の結果と同じ dp[r, c, 0, 0] = dp[r + 1, c - 1, 0, 0]; // 片方の元文字列を使わないときは、使わない側を1つ内側にした結果と同じ dp[r, c, 0, 1] = dp[r + 1, c, 0, 1]; dp[r, c, 1, 0] = dp[r, c - 1, 1, 0]; // どちらの元文字列も使うとき dp[r, c, 1, 1] = dp[r + 1, c, 1, 1] + dp[r, c - 1, 1, 1]; dp[r, c, 1, 1] -= dp[r + 1, c - 1, 1, 1]; // 重複を排除 if (s[r] == s[c]) { // s[r]とs[c]が同じ文字なら、両方の元文字列を使うパターンに // 内側の結果すべてが加算される dp[r, c, 1, 1] += dp[r + 1, c - 1, 1, 1]; dp[r, c, 1, 1] += dp[r + 1, c - 1, 1, 0]; dp[r, c, 1, 1] += dp[r + 1, c - 1, 0, 1]; dp[r, c, 1, 1] += dp[r + 1, c - 1, 0, 0]; }
最終的な実装は省略。