Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> C++

Can I Win ใน C++ : เกมเลือกตัวเลขไม่ซ้ำกันเพื่อให้ Sum >= Target


เกม "100 Game" แบบปรับเปลี่ยน: มีตัวเลข 1 ถึง maxChoosableInteger (ไม่ซ้ำ ไม่ได้เลือกซ้ำ) 2 ผู้เล่นสลับกันเลือก ตัวเลขที่เลือกจะบวกเข้า Running Total ผู้ที่ทำให้ Total ≥ desiredTotal **ชนะ**

สมมติทั้งคู่เล่น **Optimal** หาว่า **Player 1 (First Player) ชนะได้หรือไม่**

Constraints

  • maxChoosableInteger ≤ 20 -> State Space = 2^20 ≈ 1 ล้าน (พอสำหรับ Bitmask DP)
  • desiredTotal ≤ 300

ตัวอย่าง

maxChoosableInteger = 20, desiredTotal = 11 -> false

Player 1 เลือกอะไรก็ตาม Player 2 ตอบโต้ได้ทำให้ Player 1 แพ้

แนวทาง: Minimax + Bitmask DP (Memoization)

State = mask (Bitmask ของตัวเลขที่ **ยังเหลือเลือกได้**) + remainingTotal

  • solve(mask, remaining) = Current Player ชนะได้หรือไม่จาก State นี้
  • Base: remaining <= 0 -> Current Player **แพ้** (เพราะ Player ก่อนหน้าทำให้ถึง Target แล้ว)
  • Transition: ลองเลือกทุกตัว i ที่ยังเหลือ (bit i = 1)
    • ถ้า !solve(mask ^ (1<<i), remaining - i) -> 对手แพ้ -> เราชนะ -> Return true
  • Memoize ผลลัพธ์ใน dp[mask]

ตัวอย่างโค้ด C++

#include <bits/stdc++.h>
using namespace std;

class Solution {
public:
    int dp[1 << 21]; // 2^21 states, -1=uncomputed, 0=false, 1=true

    bool solve(int n, int remaining, int mask) {
        if (remaining <= 0) return false; // Previous player won
        if (dp[mask] != -1) return dp[mask];

        bool canWin = false;
        for (int i = 1; i <= n; i++) {
            if (!(mask & (1 << i))) continue; // Already used (bit=0 means used in this encoding)
            // Wait: mask encoding in original code: bit=1 means available? Let's check.
            // Original: if(!((mask >> i) & 1)) -> bit i is 0 -> available
            // So mask: 0=available, 1=used. We'll stick to original logic.
        }
        // Corrected based on original logic:
        // mask bit i = 1 means USED. 0 means AVAILABLE.
    }

    // Let's rewrite clearly:
    bool solve(int n, int remaining, int mask) {
        if (remaining <= 0) return false; // Opponent just won
        if (dp[mask] != -1) return dp[mask];

        bool ret = false;
        for (int i = 1; i <= n; i++) {
            if (!((mask >> i) & 1)) { // Bit i is 0 -> Available
                // Choose i, mark as used (set bit i to 1)
                // Next state: opponent's turn with remaining - i
                // If opponent CANNOT win from there -> WE WIN
                ret |= (!solve(n, remaining - i, mask ^ (1 << i)));
            }
        }
        return dp[mask] = ret;
    }

    bool canIWin(int n, int desiredTotal) {
        if (desiredTotal <= 0) return true;
        fill(begin(dp), end(dp), -1);
        // Quick check: sum of all numbers < desiredTotal -> impossible to reach -> first player loses
        if (desiredTotal > n * (n + 1) / 2) return false;
        return solve(n, desiredTotal, 0); // mask=0: all bits 0 -> all available
    }
};

Input / Output

Input:  10 11
Output: 0 (false)

ความซับซ้อน (Complexity)

  • States: 2^n (n ≤ 20 -> ~1M)
  • Time: O(n * 2^n) — แต่ละ State ลูป n ตัว
  • Space: O(2^n) — DP Array

จุดสำคัญ (Key Points)

  • Bitmask Encoding: Bit = 1 = Used, Bit = 0 = Available (ตามโค้ดต้นฉบับ)
  • Minimax Logic: ret |= !solve(...) -> เราชนะได้ถ้ามีการเลือกทำให้ **คู่ต่อสู้แพ้**
  • Pruning: desiredTotal > sum(1..n) -> False ทันที

Classic Game Theory + Bitmask DP Problem ครับ