zoukankan      html  css  js  c++  java
  • 【洛谷5363】[SDOI2019] 移动金币(动态规划)

    点此看题面

    大致题意:(n)个格子,让你摆放(m)个金币。二人博弈,每次选择一个金币向左移任意格,无法移动者输。问有多少种方案使先手必胜。

    阶梯(Nim)

    阶梯(Nim)的基本模型,就是有(n)层楼梯(从(0sim n-1)编号),每层楼梯上有若干石子,每次可以取任一层楼梯上任意多个石子到下一层,无法移动者输。

    它的解决方法就是,去掉所有编号为偶数的楼梯,然后对剩下的这些编号为奇数的楼梯当成普通(Nim)来做。

    原理是,如果一人移动编号为偶数的楼梯上的石子到下一层,如移动第(2n)层的(a)个石子到第(2n-1)层,那么无论何时对方都可以把这(a)个石子接着从第(2n-1)层移动到第(2n-2)层,因为(2n-2)层是必然存在的。

    所以,移动偶数层的石子相当于是无效的,就可以直接忽略。

    此题转化

    对于这道题,我们可以发现,如果把每两个金币之间的空格当作一堆石子,这就是一个典型的阶梯(Nim)

    也就是说,若要先手必胜,就要满足奇数堆石子个数异或值不为(0)

    这显然不好求,所以我们可以转而求异或值为(0)的方案数,再用总方案数(C_n^m)减去它即为答案。

    动态规划

    考虑如何求异或值为(0)的方案数。

    可以在二进制下逐位(DP)

    我们设(f_{i,j})表示满足二进制下从最高位至右数第(i)位异或值为(0),剩余石子总数为(j)的方案数

    那么对于(f_{i,j})的转移,我们可以枚举有(k)个奇数堆石子个数二进制下第(i)位为(1),其中因为要使异或值为(0),因此(k)为偶数。

    转移方程为:

    [f_{i,j}=sum C_{t1}^k imes f_{i+1,j+k imes2^i} ]

    其中(t1)表示奇数堆的个数。

    计算答案

    我们可以枚举满足所有奇数堆每一位异或值为(0)时所剩的石子个数(i),这就是偶数堆的石子个数。

    则用插板法就可以求出异或值为(0)的方案数为:

    [sum_{i=1}^{n-m}C_{i+t0-1}^{t0-1} imes f_{0,i} ]

    其中(t0)表示偶数堆的个数。

    最后用(C_n^m)减去这一方案数就是答案。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int 
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 150000
    #define M 50
    #define LN 20
    #define X 1000000009
    #define Qinv(x) Qpow(x,X-2)
    #define C(x,y) (1LL*Fac[x]*IFac[y]%X*IFac[(x)-(y)]%X)//组合数
    using namespace std;
    int n,m,Fac[N+5],IFac[N+5],f[LN+5][N+5];
    I int Qpow(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
    int main()
    {
    	RI i,j,k,t0,t1,res=0;scanf("%d%d",&n,&m),t0=(m>>1)+1,t1=m+1>>1;//计算偶数堆和奇数堆个数
    	for(k=max(n,2*m),Fac[0]=i=1;i<=k;++i) Fac[i]=1LL*Fac[i-1]*i%X;//初始化阶乘
    	for(IFac[k]=Qinv(Fac[k]),i=k-1;~i;--i) IFac[i]=1LL*IFac[i+1]*(i+1)%X;//初始化阶乘逆元
    	for(f[LN][n-m]=1,i=LN-1;~i;--i) for(j=0;j<=n-m;++j)//动态规划
    		for(k=0;k<=t1&&j+(k<<i)<=n-m;k+=2) f[i][j]=(C(t1,k)*f[i+1][j+(k<<i)]+f[i][j])%X;
    	for(i=0;i<=n-m;++i) res=(C(i+t0-1,t0-1)*f[0][i]+res)%X;//计算异或值为0的方案数
    	return printf("%d",(C(n,m)-res+X)%X),0;//输出答案
    }
    
  • 相关阅读:
    linux系统中fdisk命令进行磁盘分区
    linux系统中挂载mount命令、umount命令
    linux系统中du命令
    linux系统中独立冗余磁盘阵列RAID
    linux vmware虚拟机添加硬盘
    linux系统中部署raid10磁盘阵列
    linux系统中配置磁盘容量配额服务(quota)
    linux系统中添加swap交换分区、删除swap交换分区
    linux系统中软链接和硬链接
    调试流程(暂定)
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5363.html
Copyright © 2011-2022 走看看