[bzoj3492]Binary Dodgeball

25 篇文章 1 订阅
13 篇文章 0 订阅

结论

我把乘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。
这样的数有多少个呢? n2kn2k+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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值