ACM训练联盟周赛 C题 Alice和Bob的Nim游戏
题目描述
众所周知,Alice和Bob非常喜欢博弈,而且Alice永远是先手,Bob永远是后手。
Alice和Bob面前有3堆石子,Alice和Bob每次轮流拿某堆石子中的若干个石子(不可以是0个),拿到所有石子中最后一个石子的人获胜。这是一个只有3堆石子的Nim游戏。
Bob错误的认为,三堆石子的Nim游戏只需要少的两堆的石子数量加起来等于多的那一堆,后手就一定会胜利。所以,Bob把三堆石子的数量分别设为 {k,4k,5k}(k>0)。
现在Alice想要知道,在k 小于 2^n 的时候,有多少种情况先手一定会获得胜利。
输入
一个整数n(1 \le n \le 2 \times 10^91≤n≤2×109)。
输出
输出先手胜利的可能情形数。答案对10^9+7109+7取模。
样例输入
3
样例输出
2
题目来源
Nim博弈论,当先手为K^(4K)^(5K) == 0时必输。要求先手必赢,让总的数量减去必输的数量就是答案了。当K^(4K)^(5K) == 0时,K^(4K) == 5K,又因为K+2K=5K,所以K的二进制相隔一个位置不能同时为1.
则f(n) = f(n-1) + f(n-3) + f(n-4) + 2,总的数量为2n-1。
有了公式用矩阵快速幂就可以求出来了。
1 #include <bits/stdc++.h> 2 #define ll long long 3 using namespace std; 4 const ll mod = 1e9+7; 5 struct mat{ 6 ll m[5][5]; 7 mat() { 8 memset(m, 0, sizeof(m)); 9 } 10 }; 11 12 mat mul(mat &A, mat &B) { 13 mat C; 14 for(int i = 0; i < 5; i ++) { 15 for(int j = 0; j < 5; j ++) { 16 for(int k = 0; k < 5; k ++) { 17 C.m[i][j] = (C.m[i][j] + A.m[i][k]*B.m[k][j]) % mod; 18 } 19 } 20 } 21 return C; 22 } 23 24 mat pow(mat A, ll n) { 25 mat B; 26 for(int i = 0; i < 5; i ++) B.m[i][i] = 1; 27 while(n) { 28 if(n&1) B = mul(B,A); 29 A = mul(A,A); 30 n >>= 1; 31 } 32 return B; 33 } 34 ll pow(ll n) { 35 ll x = 2, y = 1; 36 while(n) { 37 if(n&1) y = (y*x)%mod; 38 x = x*x %mod; 39 n >>= 1; 40 } 41 return y; 42 } 43 int main() { 44 ll n; 45 cin >> n; 46 if(n <= 2) return 0*printf("0\n"); 47 else if(n == 3) return 0*printf("2\n"); 48 else if(n == 4) return 0*printf("7\n"); 49 mat A; 50 A.m[0][0] = A.m[0][2] = A.m[0][3] = A.m[0][4] = 1; 51 A.m[1][0] = A.m[2][1] = A.m[3][2] = A.m[4][4] = 1; 52 A = pow(A, n-4); 53 ll ans = (A.m[0][0]*8+A.m[0][1]*5+A.m[0][2]*3+A.m[0][3]+A.m[0][4]*2+mod)%mod; 54 ans = (pow(n) - ans - 1 + mod) %mod; 55 printf("%lld\n",ans); 56 return 0; 57 } 58 59 // fn 1 0 1 1 1 fn-1 60 // fn-1 1 0 0 0 0 fn-2 61 // fn-2 0 1 0 0 0 fn-3 62 // fn-3 0 0 1 0 0 fn-4 63 // 2 0 0 0 0 1 2