题解:P15088 [UOI 2025 II Stage] Digital Game
Turkey_VII · · 题解
曼波~
这是一道非常容易陷入思维误区题目,如果你看到
先说结论:当
更一般的说,有定理:
定理
设
时 Us 必胜。
(此 Us 非彼 Us,仅代表题目中人名 感觉很奇怪说一下)
证明
使用数学归纳法。
1.归纳基础
当游戏进入一个短区间时,即
2.归纳假设
假设对于所有长度小于
3.归纳推导
我们现在只关注区间
引入变量:
-
cnt(l, r)$ 减少 $1 - 新的
d' = (len-1) - 2(cnt(l, r) - 1) = d + 1 = 0
此时
情况 3:
子情况 3a:存在只出现一次的数字 Us 删除它,则:
- 长度减
1 -
cnt(l,r)$ 减 $1 - 新的
d' = (len-1) - 2(\text{cnt}-1) = 1
但此时区间缩短,且由归纳假设,定理成立。
子情况 3b:所有数字都恰好出现两次
考虑子区间
- 长度
len' = len - 1 -
-
d' = (len-1) - 2 \times cnt(l, r) = (len - 2 \times cnt(l,r)) - 1 = d - 1 = -1
根据情况 2 的分析,在子区间内必然存在一个只出现一次的数字,Us 就删除它。删除后:
- 原区间长度缩短
- 新的
d'' = 1 - 满足条件,由归纳假设定理成立。
终于,我们成功证明了定理:当:
时 Us 必胜。
注意到字符串中的数字只有
所以最终我们可以得到:当
那么对于
显然我们就可以愉快的使用记忆化搜索,用一个
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int T, n;
bool r[N];
string s;
int pre(int w, int x){
for(int i = w - 1; i >= 0; i--){
if(!(x & (1 << i))) continue;
return s[i] - '0';
}
return -1;
}
int nex(int w, int x){
for(int i = w + 1; i < n; i++){
if(!(x & (1 << i))) continue;
return s[i] - '0';
}
return -1;
}
int check(int x){ //判断当前状态有没有分出胜负
int cnt = 0;
for(int i = 0; i < n; i++){
if(!(x & (1 << i))){
cnt++;
continue;
}
if(((s[i] - '0') == pre(i, x)) || ((s[i] - '0') == nex(i, x))) return 0; //有相邻的
}
if(cnt == n) return 1; //串已空
return 2;
}
bool dfs(int py, int x){ //py为0表示Vus,1表示Us
if(r[x]) return r[x]; //记忆化
int f = check(x);
if(f == 1) return r[x] = (1 ^ py);
else if(f == 0) return r[x] = (0 ^ py);
for(int i = 0; i < n; i++){
if(!(x & (1 << i))) continue;
bool flag = dfs((py ^ 1), x ^ (1 << i));
if(!flag) return r[x] = 1; //有一种情况对方必输,当前状态己方就必赢
}
return r[x] = 0; //没有则对方必赢
}
int main(){
cin >> T;
while(T--){
memset(r, 0, sizeof(r));
cin >> n;
cin >> s;
if(n >= 20){
cout << "No" << '\n';
continue;
}
int x = ((1 << n) - 1);
if(!dfs(0, x)) cout << "No" << '\n';
else cout << "Yes" << '\n';
}
return 0;
}