Game Theory
Game Theory สำหรับ Competitive Programming: Nim, Sprague-Grundy
Game Theory
Combinatorial Game Theory
เกมที่มีคุณสมบัติ:
- Two players - ผลัดกันเล่น
- Perfect information - รู้ทุกอย่างเกี่ยวกับ game state
- No chance - ไม่มี randomness
- Finite - เกมจบใน finite moves
- Normal play - คนที่ไม่สามารถเดินได้แพ้
Nim Game
กติกา
มี กอง หิน กองที่ มี ก้อน ผลัดกันหยิบหินจากกองใดก็ได้ (อย่างน้อย 1 ก้อน) คนที่ไม่สามารถหยิบได้แพ้
Nim-Sum (XOR)
- ถ้า nim-value : Second player wins (P-position)
- ถ้า nim-value : First player wins (N-position)
bool firstPlayerWins(vector<int>& piles) {
int xorSum = 0;
for (int pile : piles) {
xorSum ^= pile;
}
return xorSum != 0;
}
Winning Move in Nim
pair<int, int> winningMove(vector<int>& piles) {
int xorSum = 0;
for (int pile : piles) xorSum ^= pile;
if (xorSum == 0) return {-1, -1}; // No winning move
for (int i = 0; i < piles.size(); i++) {
int target = piles[i] ^ xorSum;
if (target < piles[i]) {
return {i, piles[i] - target}; // Remove this many from pile i
}
}
return {-1, -1};
}
Sprague-Grundy Theorem
Grundy Number (Nimber)
สำหรับ game state :
โดยที่ = minimum excludant = ค่า non-negative integer ที่น้อยที่สุดที่ไม่อยู่ใน
Properties
- Terminal state:
- P-position:
- N-position:
- Independent games:
Implementation
int mex(set<int>& s) {
int result = 0;
while (s.count(result)) result++;
return result;
}
// Example: Subtract game (can subtract 1, 2, or 3)
vector<int> grundy;
void computeGrundy(int maxN) {
grundy.resize(maxN + 1);
grundy[0] = 0; // terminal state
for (int n = 1; n <= maxN; n++) {
set<int> reachable;
for (int move = 1; move <= 3 && move <= n; move++) {
reachable.insert(grundy[n - move]);
}
grundy[n] = mex(reachable);
}
}
Classic Games
1. Nim Variants
Staircase Nim
piles arranged as staircase. Can move any number from pile to pile (pile 0 is removed).
Solution: XOR of odd-indexed piles
bool stairNim(vector<int>& piles) {
int xorSum = 0;
for (int i = 0; i < piles.size(); i += 2) {
xorSum ^= piles[i];
}
return xorSum != 0;
}
Poker Nim
Same as Nim but can also add stones (up to limit).
Solution: Same as Nim (adding doesn’t help)
2. Subtraction Game
Can remove stones from set (e.g., )
void subtractGame(int maxN, vector<int>& S) {
vector<int> g(maxN + 1, 0);
for (int n = 1; n <= maxN; n++) {
set<int> reachable;
for (int s : S) {
if (s <= n) {
reachable.insert(g[n - s]);
}
}
g[n] = mex(reachable);
}
}
3. Wythoff’s Game
Two piles. Can remove any from one pile, OR remove equal amounts from both.
Solution: P-positions are for
โดยที่ (golden ratio)
bool wythoff(int a, int b) {
if (a > b) swap(a, b);
double phi = (1 + sqrt(5)) / 2;
int k = b - a;
int expected = (int)(k * phi);
return a != expected; // First player wins if not P-position
}
4. Euclid’s Game
Two numbers . Can subtract any positive multiple of smaller from larger (result must be positive). Last to move wins.
bool euclidGame(long long a, long long b) {
while (true) {
if (a > b) swap(a, b);
if (a == 0) return false; // Current player loses
if (b >= 2 * a) return true; // Can control the game
b -= a;
// Role switches
}
}
5. Green Hackenbush
Game on graph. Remove edge (and disconnected parts). Last to move wins.
- Tree: Grundy = XOR of edge Grundy values
- String (path): length mod 2
Multiple Games
เมื่อเล่นหลายเกมพร้อมกัน (เลือกเกมใดก็ได้ที่ยังไม่จบ):
bool multipleGames(vector<vector<int>>& games) {
int totalG = 0;
for (auto& game : games) {
totalG ^= computeGrundy(game);
}
return totalG != 0;
}
Tips
- Look for patterns - Grundy numbers often have periodic patterns
- Small cases first - Compute for small inputs to find pattern
- XOR property - Key insight for combining independent games
- P and N positions - P = Previous player wins (current loses), N = Next player wins