结论
我把乘2^k变成除以的话和原游戏当然是等价的。
这样的话我们把每个数二进制都写出来,每次就是去掉末尾几个0。
按照除lowbit部分分组,不同组之间互相独立。
每组的游戏可以这样描述:
有一群石子堆,每次从一个石子堆拿走至少一颗石子,若存在两堆相同的石子堆,则一起移走。
没有后面那个的话很容易知道就是nim游戏。
但其实我们可以把移走的两堆绑在一起,那么一个人操作其中一堆,另一个人就对着另外一堆进行相同操作,游戏局面不变(大概用了阶梯nim的思想)
这样思考这两堆石子堆就可以不移走。
那就是彻彻底底的nim游戏。
因此我们知道后手必胜就是上述过程异或和为0。
求解我们考虑二分+判定,于是问题变成了求<=N的有多少n合法。
这里为了防止懵逼,来定义一下合法的n。
对于一个1<=i<=n
i的贡献是末尾有多少连续0(在二进制下,如10100贡献为2)
把贡献异或起来,是0那么n是合法。
数位DP
我们考虑对于一个n,k的贡献。(即所有贡献为k的数有多少个,只关心奇偶性即可)
贡献k那么说明二进制下最末是100……0,有k个0。
这样的数有多少个呢?
⌊n2k⌋−⌊n2k+1⌋
我们只关心这个式子的奇偶性。可以发现就是n的第k位和第k+1位是否相同,相同就是奇数否则是偶数。
这样就好算了!
设f[i,j,k,0~1]表示做完第i位,第i位是j,当前贡献异或和为k,目前这个数是=N的前i位还是
<
<script type="math/tex" id="MathJax-Element-84"><</script>N的前i位。
转移也很简单。
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxd=50,mx=64;
ll l,r,mid;
int f[maxd+10][2][100][2],ws[maxd+10];
int i,j,k,t,n,m;
int check(ll N){
int i,j,k,l,r,t;
fo(i,0,maxd)
fo(j,0,1)
fo(k,0,mx)
fo(t,0,1)
f[i][j][k][t]=0;
ll K=N;
fo(i,0,maxd){
ws[i]=K%2;
K/=2;
}
if (ws[maxd]==1){
f[maxd][1][0][1]=1;
f[maxd][0][0][0]=1;
}
else f[maxd][0][0][1]=1;
fd(i,maxd-1,0)
fo(j,0,1)
fo(k,0,mx)
fo(t,0,1){
if (j<ws[i]){
f[i][j][k][0]+=f[i+1][t][k^(i*(j!=t))][0]+f[i+1][t][k^(i*(j!=t))][1];
}
else if (j==ws[i]){
f[i][j][k][0]+=f[i+1][t][k^(i*(j!=t))][0];
f[i][j][k][1]+=f[i+1][t][k^(i*(j!=t))][1];
}
else{
f[i][j][k][0]+=f[i+1][t][k^(i*(j!=t))][0];
}
}
r=0;
fo(i,0,1)
fo(j,0,1)
r+=f[0][i][0][j];
return r-1;
}
int main(){
scanf("%d",&m);
l=0;r=62064730374;
while (l<r){
mid=(l+r)/2;
if (check(mid)<m) l=mid+1;else r=mid;
}
check(1);
printf("%lld\n",l);
}