สมมุติว่าในเกมที่ชื่อว่า "100 เกม" ผู้เล่นสองคนผลัดกันบวกกับจำนวนเต็มใดๆ จาก 1 ถึง 10 ผู้เล่นที่ทำให้ยอดรวมวิ่งเป็นคนแรกหรือ เกิน 100 เขา / เธอชนะ แล้วถ้าเราเปลี่ยนเกมไม่ให้ผู้เล่นใช้จำนวนเต็มซ้ำล่ะ
ตัวอย่างเช่น หากผู้เล่นสองคนผลัดกันดึงจากกลุ่มตัวเลขทั่วไปที่ 1..15 โดยไม่มีการเปลี่ยนจนกว่าจะถึงยอดรวม>=100
ดังนั้น สมมติว่าให้จำนวนเต็ม maxChoosableInteger และจำนวนเต็มอื่นที่ต้องการ ให้พิจารณาว่าผู้เล่นคนแรกที่ย้ายสามารถบังคับให้ชนะ สมมติว่าผู้เล่นทั้งสองเล่นอย่างเหมาะสมที่สุด
เราสามารถสมมติได้เสมอว่า maxChoosableInteger จะไม่มากกว่าค่า 20 และผลรวมที่ต้องการจะไม่เกิน 300 ดังนั้นหากอินพุตคือ maxChooseableInteger =20 และผลรวมที่ต้องการคือ 11 ดังนั้น ผลลัพธ์จะเป็นเท็จ ไม่ว่าผู้เล่นคนแรกจะเลือกใคร ผู้เล่นคนแรกก็จะแพ้
เพื่อแก้ปัญหานี้ เราจะทำตามขั้นตอนเหล่านี้ -
-
สร้างอาร์เรย์ชื่อ dp ขนาด 2^21
-
กำหนดวิธีการแก้ปัญหา () ซึ่งจะใช้เวลา n, s และมาสก์
-
ถ้า s <=0 ให้คืนค่าเท็จ
-
ถ้า dp[mask] ไม่ใช่ -1 ให้คืนค่า dp[mask]
-
set ret :=false
-
สำหรับฉันอยู่ในช่วง 1 ถึง n
-
ถ้า (เปลี่ยนหน้ากากฉันกัดไปทางขวา) เป็นเลขคี่
-
ret :=ret OR (ผกผันของ Solve(n, s – i, mask XOR 2^i))
-
-
-
dp[mask] :=ยกเลิก
-
รีเทิร์น
-
จากวิธีหลัก ให้ทำดังนี้
-
ถ้าต้องการรวม <=0 แล้วคืนค่าจริง
-
สำหรับผมอยู่ในช่วง 0 ถึง 2^21
-
dp[i] :=-1
-
-
หากต้องการทั้งหมด> (ผลรวมของตัวเลข n ตัวแรก) ให้คืนค่าเท็จ
-
กลับแก้(n, ที่ต้องการรวม, 0)
ตัวอย่าง (C++)
ให้เราดูการใช้งานต่อไปนี้เพื่อความเข้าใจที่ดีขึ้น -
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
int dp[1 << 21];
bool solve(int n, int s, int mask){
if(s <= 0) return false;
if(dp[mask] != -1) return dp[mask];
bool ret = false;
for(int i = 1; i <= n; i++){
if(!((mask >> i) & 1)){
ret |= (!solve(n, s - i, (mask ^ (1 << i))));
}
}
return dp[mask] = ret;
}
bool canIWin(int n, int desiredTotal) {
if(desiredTotal <= 0) return true;
for(int i = 0; i < (1 << 21); i++)dp[i] = -1;
if(desiredTotal > (n * (n + 1)/ 2))return false;
return solve(n, desiredTotal, 0);
}
};
main() {
Solution ob;
cout << (ob.canIWin(10,11));
} อินพุต
10 11
ผลลัพธ์
0