E - Remove Pairs(记忆化搜索or状态压缩dp)
该题是一个典型的状态压缩dp的题,但也可以用记忆化搜索来写。
该题题意:
A和B在玩游戏,桌子上有n个牌,每个牌的正面和背面都写上数字。每次可以进行如下操作:
- 将两张正面和背面至少有一面相同的牌丢弃。
根据以上规则,两个人轮流丢牌,直到丢不了为止,丢不了的那个人输。
A先手。
数据范围:
- 1 < = N < = 18 1 <= N <= 18 1<=N<=18
- 1 < = A i , B i < = 1 0 9 1 <= A_i,B_i <= 10^9 1<=Ai,Bi<=109
- 所有输入的都是整数。
解题思路:
首先要考虑到状态转移。这个题本身是个博弈的题,那么我们可以考虑到当下一个人没法选择两张牌的时候,那么就是当前状态赢。既然要考虑到状态转移,那么就需要进行状态转移的基本方法:
- 起始状态: 起始状态下就是一个都没选,那就对应的每一个牌的状态都为1 (代码实现上可以用((1 << n) - 1) 来实现)。
- 终止状态: 当下一步没法进行选取的时候,就是终止状态。
- 状态转移方程: 我们可以看出来,每到下个一状态就是由上一个状态减去两张牌得来的,那么就可以得到
nex = x ^ (1<<i) ^ (1 << j)
,这样得到的nex就是下一个状态需要的。
整个思路可以这样理解,就是玩家会最优地选择策略,那么如果下个状态出现对手必败的情况,那么他就一定会到达那。
代码实现:
代码实现上,可以用到记忆化搜索和状态压缩。
记忆化搜索:
const int N = 20;
const int M = 1 << 18;
pair<int,int> arr[N];
bool user[M];
int f[M],n;
int dfs(int x){
if(user[x]) return f[x]; //记忆化
for(int i = 0; i < n - 1; i++){
if(!((1 << i) & x)) continue; // 看是否已经被丢掉了
for(int j = i + 1; j < n; j++){
if(!((1 << j) & x)) continue; // 和上面一样
if(arr[i].first != arr[j].first && arr[i].second != arr[j].second) continue; // 判断是否能被丢掉
int nex = x ^ (1 << i) ^ (1 << j); // 丢掉这两个张牌
f[x] |= (!dfs(nex)); // 因为到下一个状态的时候,如果对方赢的话,那么我们就输,如果对方输的话,我们就赢
}
}
user[x] = true; // 标记一下这个点是否被遍历过了。
return f[x];
}
int main (void){
cin >> n;
for(int i = 0; i < n; i++) cin>>arr[i].first >> arr[i].second;
dfs((1 << n) -1);
cout << (f[((1 << n) - 1)] ? "Takahashi" : "Aoki");
return 0;
}
状态压缩dp:
const int N = 20;
const int M = 1 << 18;
pair<int,int> arr[N];
bool user[M];
int f[M], n;
int main (void){
cin >> n;
for(int i = 0; i < n; i++) cin>>arr[i].first >> arr[i].second;
int S = (1 << n) - 1;
for(int s = 0; s <= S; s++){ // 状态压缩 遍历从0 到 1 << n - 1,
for(int i = 0; (1 << i) <= s; i++){
if(!(s & (1 << i))) continue; // 判断这个点是否已经被丢掉了
for(int j = i + 1; (1 << j) <= s; j++){
if(!(s & (1 << j))) continue; // 和上一条一样
if(arr[i].first != arr[j].first && arr[i].second != arr[j].second) continue; // 判断是否能被丢掉
int nex = s ^ (1 << i) ^ (1 << j); // 能丢就丢
f[s] |= !f[nex]; // 判断丢掉后的这个点是赢还是输,如果对面赢,我们就输,如果对面输,我们就赢。
}
}
}
cout << (f[S] ? "Takahashi" : "Aoki") << endl;
return 0;
}
总结:
这个题,当初看到的时候,以为是个博弈题,但看到这个数据范围后,也没想到什么解题思路,当时就直接放弃了,但现在看来,他好像是个博弈dp(看别人的,之前没听说过这个dp),但是仔细看来和博弈关系不大,就是一个典型的状态压缩dp,以后看到这种时间复杂度的题,就可以想到是个二进制状态压缩的题。