P6819 PA 2012 Binary Dodgeball Sol
难难。
题目链接。
判定
先考虑如何判定一个 \(n\) 合不合法。
发现题目是 \(\times 2^p\),因此考虑二进制。显然的,如果两个数字可以消去,当且仅当两个数字 \(lowbit\) 前面的相等。
举例
10110->1011000显然是合法的,而都可以视作1011而来
而10110->101110则显然没有合法的方式
同时容易观察到的是,加 0 和删 0 是不影响结果的。
而我们不妨对于每一个 \(lowbit\) 进行分组,则问题变成有若干组数字堆,每一个数字堆有若干个数,每一次可以对于一个数,选择删去 0,第一个无法删去 0 的人输。因此不妨视作石子游戏,只是新增加了一个规则:如果组内出现石子个数相同的情况,那么删去两堆。
考虑基础的 \(Nim\) 游戏,我们发现如果两个堆的石子个数想等,那么不会进入考虑范围。也就是说,实际上这个新加的规则是没有影响的。因此 \(Nim\) 和依然通用。
所以,问题也就变成末尾的 0 的个数的异或和是否为 0,为 0 则后手必胜。
查找
假设我们希望倒数 \(i\) 位都是 0。那么显然方案数为 \(\lfloor \frac{n}{2^i} \rfloor - \lfloor \frac{n}{2^{i+1}} \rfloor\)。发现这个也就是说 \(n\) 的第 \(i\) 位是否与 \(n\) 的第 \(i+1\) 位相等。
考虑使用二分查找 \(n\),找到最小的 \(n\) 满足条件。不妨使用数位DP,定义 \(f[i][j][k][0/1]\) 表示第 \(i\) 位,填 \(j\),异或和为 \(k\),是否贴到上界。转移显然。
Code
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define File(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout)
#define LL long long
#define fi first
#define se second
LL f[66][3][66][3];
LL solve(LL x){
memset(f,0,sizeof f);
f[60][0][0][1] = 1;
for(int i=60;i>=1;i--)
for(int j=0;j<2;j++)
for(int k=63;k>=0;k--){
if((x >> (i-1)) & 1){
f[i-1][1][k ^ ((j^1) * (i-1))][1] += f[i][j][k][1];
f[i-1][0][k ^ (j * (i-1))][0] += f[i][j][k][1];
}
else
f[i-1][0][k ^ (j * (i-1))][1] += f[i][j][k][1];
f[i-1][1][k ^ ((j^1) * (i-1))][0] += f[i][j][k][0];
f[i-1][0][k ^ (j * (i-1))][0] += f[i][j][k][0];
}
LL res = 0;
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
res += f[0][i][0][j];
return res - 1;
}
int main(){
IOS;
int k;
cin >> k;
LL l = 1,r = (1ll<< 60);
LL fnd = 0;
while(l <= r){
LL mid = l + r >> 1;
LL res = solve(mid);
if(res >= k){
r = mid - 1;
fnd = mid;
}
else
l = mid + 1;
}
cout << fnd;
return 0;
}

浙公网安备 33010602011771号