zoukankan      html  css  js  c++  java
  • 【SDOI201】黑白棋 /【XSY3064】小奇的博弈(博弈,nim,dp,组合数)

    显然,如果白棋往左,黑棋往右,最后肯定会两两碰在一起,就像这样:

    在这里插入图片描述

    红框框起来的是会碰在一起的棋子。(我们把会碰到一起的棋子称为一对棋子)

    如下图就是碰在一起的一种情况:

    在这里插入图片描述

    那么现在假设是 A A A 遇到了这种情况,那么无论他操作的是白棋或黑棋,他肯定会输。因为另一个人可以操控棋子跟着 A A A 的棋子走,一直保持棋子两两紧逼的状态,直到所有棋子都堆在一边,这时 A A A 就无路可走了,失败。

    不妨设开始前每对棋子之间的距离为 a 1 , a 2 , … , a k 2 a_1,a_2,dots,a_{frac{k}{2}} a1,a2,,a2k,那么原题就可以转化成一个 k-nim 游戏:现在有 k 2 frac{k}{2} 2k 堆石子,第 i i i 堆石子的石子个数是 a i a_i ai,现在两人轮流进行如下操作:在这之中任意选取 1 ∼ d 1sim d 1d 堆石子,对于选取的这几堆石子中的每堆石子,能拿走任意正整数个石子。最后不能进行操作为失败。

    这种问题有一个结论:将当前状态的 a i a_i ai 用二进制表示,设 s i s_i si 表示 a 1 ∼ a k 2 a_1sim a_{frac{k}{2}} a1a2k 中二进制下第 i i i 1 1 1 的个数, s i ≡ s i ′ ( m o d k + 1 ) s_iequiv s_i'pmod {k+1} sisi(modk+1)。若所有的 s i ′ s_i' si 都为 0 0 0,则这个状态为必败状态,否则为必胜状态。

    证明:

    1. a i a_i ai 全部为 0 0 0 的状态是必败状态,且此时所有的 s i ′ s_i' si 都为 0 0 0

    2. 设状态 P P P s i ′ s_i' si 全部为 0 0 0 的状态,状态 N N N 是存在 s i ′ s_i' si 不为 0 0 0 的状态。

      假设当前状态为 P P P 状态。

      显然,由于我至少要在一个堆里面取石子,所以某些 a i a_i ai 的一定会改变。所以首先,某些 s i s_i si 一定会改变。然后又由于一次最多只能改变 d d d 个堆,所以对于每一个 s i s_i si,最多只会加上或减去 d d d。又由于一开始 s i ≡ 0 ( m o d d + 1 ) s_iequiv0pmod{d+1} si0(modd+1),所以变化后的 s i s_i si 一定不可能满足 s i ≡ 0 ( m o d d + 1 ) s_iequiv0pmod{d+1} si0(modd+1)

      也就是说,对于任意的 P P P 状态,一定只能转移成 N N N 状态。

    3. 假设当前状态为 N N N 状态,那么我们要证明的目标是:对于任意的 N N N 状态,一定存在一种转移方式转移成 P P P 状态。

      我们从高位到低位地考虑所有的二进制位。显然,最高位的石子我们要全部取走。假设当前位为第 i i i 位,且已经满足了 s j ′ = 0 s_j'=0 sj=0 j > i j>i j>i),并且改变了 m m m 个堆的石子数量( m ≤ d mleq d md)。

      那么我们现在就是要证明:是否有一种操作,能使得 s i ′ = 0 s_i'=0 si=0,且新改变的堆加上以前改变过的堆的总堆数 m ′ ≤ d m'leq d md。(类似数学归纳法)

      有一个比较显然的性质:对于那些已经改变的 m m m 堆,我改变这些堆的低位不会影响到高位的 s j ′ s_j' sj

      设在这 m m m 堆中,每一堆所含石子个数的第 i i i 位上共有 a a a 1 1 1 b b b 0 0 0

      分情况讨论:

      1. a ≥ s i ′ ageq s_i' asi。那我们就可以在这 m m m 堆中找到 s i ′ s_i' si 个第 i i i 位为 1 1 1 的堆并取走这一位所代表的石子(也就是 2 i 2^i 2i 个石子),显然根据我们刚刚提到的性质,取走这些石子不会影响高位的 s j ′ s_j' sj。(也就是让 s i ′ ← 0 s_i'gets 0 si0

      2. b ≥ ( d + 1 ) − s i ′ bgeq (d+1)-s_i' b(d+1)si。那我们就可以在这 m m m 堆中找到 ( d + 1 ) − s i ′ (d+1)-s_i' (d+1)si 个第 i i i 位为 0 0 0 的堆并加上这一位所代表的石子(也就是 2 i 2^i 2i 个石子)(就算加上了这些石子,对于这一堆来说,石子总数还是减的,因为我们一开始减去了最高位所代表的石子),显然根据我们刚刚提到的性质,取走这些石子不会影响高位的 s j ′ s_j' sj。(也就是让 s i ′ ← d + 1 s_i'gets d+1 sid+1

      3. 上述两种情况都不满足,也就是说 a < s i ′ a< s_i' a<si,那么我们先取走这 a a a 堆中第 i i i 位的石子,然后再从这 m m m 堆之外的堆里面找 s i ′ − a s_i'-a sia 个第 i i i 位为 1 1 1 的堆并取出它第 i i i 位的石子,总堆数变成了 m + s i ′ − a = b + s i ′ ≤ d + 1 − s i ′ + s i ′ = d + 1 m+s_i'-a=b+s_i'leq d+1-s_i'+s_i'=d+1 m+sia=b+sid+1si+si=d+1,所以这种取法是合法的。

      综上所述,我们可以保证对于任意的 N N N 状态,一定存在一种转移方式转移成 P P P 状态。

    综上所述,我们就可以证明 P P P 状态是必败状态, N N N 状态是必胜状态,结论成立。

    接下来就是如何计算方案。

    d p ( i , j ) dp(i,j) dp(i,j) 表示前 i − 1 i-1 i1 位每一位的异或和均为 0 0 0,已经有 j j j 个石子的方案数。(也就是必败方案数)

    枚举 a 1 ∼ a k 2 a_1sim a_frac{k}{2} a1a2k 的第 i i i 位共有多少个 1 1 1,且满足个数是 ( d + 1 ) (d+1) (d+1) 的倍数(也就是枚举 ( d + 1 ) (d+1) (d+1) x x x 倍)。

    然后在 k 2 frac{k}{2} 2k 个堆里面取 x ( d + 1 ) x(d+1) x(d+1) 个,把他们的第 i i i 位设为 1 1 1,总方案数是 ( k 2 x ( d + 1 ) ) dbinom{frac{k}{2}}{x(d+1)} (x(d+1)2k)

    最后我们还要枚举堆在哪里,也就是 ( n − j − k 2 k 2 ) dbinom{n-j-frac{k}{2}}{frac{k}{2}} (2knj2k)

    然后 dp 一下就好了。

    代码如下:

    #include<bits/stdc++.h>
    
    #define LN 20
    #define K 210
    #define N 10010
    #define ll long long
    #define int long long
    #define mod 1000000007
    
    using namespace std;
    
    int n,k,d;
    int C[N][K],dp[LN][N];
    
    signed main()
    {
    	scanf("%lld%lld%lld",&n,&k,&d);
    	C[0][0]=1;
    	for(int i=1;i<=n;i++)
    	{
    		C[i][0]=1;
    		for(int j=1;j<=200;j++)
    			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
    	}
    	dp[0][0]=1;
    	for(int i=0;i<=16;i++)
    	{
    		ll t=(1ll<<i);
    		for(int j=0;j<=n-k;j++)
    			for(int x=0;t*x*(d+1)<=n-k&&x*(d+1)<=k/2;x++)
    				dp[i+1][j+t*x*(d+1)]=(dp[i+1][j+t*x*(d+1)]+(1ll*dp[i][j]*C[k/2][x*(d+1)])%mod)%mod;
    	}
    	ll ans=0;
    	for(int i=0;i<=n-k;i++)
    		ans=(ans+1ll*dp[17][i]*C[n-i-k/2][k/2]%mod)%mod;
    	printf("%lld
    ",((C[n][k]-ans)%mod+mod)%mod);
    	return 0;
    }
    
  • 相关阅读:
    freemarker模版引擎技术总结
    ajax跨域访问数据
    mysql 导入 大sql文件
    Python获取并输出当前日期时间
    Pandas dataframe数据写入文件和数据库
    java DTO 转 POJO
    vs2008 c#项目调试dll源码,问题:“若要调试此模块,请将其项目生成配置更改为“调试”模式” 的解决方案
    sqlserver 2008 merger语句
    sqlserver查询数据库中有多少个表,多少视图,多少存储过程,或其他对象
    sqlserver中select造成死锁
  • 原文地址:https://www.cnblogs.com/ez-lcw/p/14448657.html
Copyright © 2011-2022 走看看