WHUT第九周训练整理

WHUT第九周训练整理

写在前面的话:我的能力也有限,错误是在所难免的!因此如发现错误还请指出一同学习!

本次题解感谢ljw提供的最后四题题解,本人实在顶不住放弃了!

索引

(难度由题目自身难度与本周做题情况进行分类,仅供新生参考!)

零、基础知识过关

一、easy:02、03、04、05、06、08、13、14、15、16

二、medium:01、07、09、10、12、17、18、21

三、hard:11、19、20、22、23、24、25

本题解报告大部分使用的是C++语言,在必要的地方使用C语言解释。

零、基础知识过关

博弈论不是我的专长,所以写这篇博客也算是大家一起学习了…

对于我来说,提到博弈论那就要与各种类型的组合游戏以及 S G ​ SG​ SG 函数挂钩了。

1. 什么是组合游戏?

在acm中组合游戏一般有以下特点:

  • 两人进行博弈
  • 两者轮流进行有效操作
  • 有效操作只取决于当前的局面
  • 无法进行有效操作时,当前者败

2. 常见的博弈类型

巴什博奕(Bash)、威佐夫博弈(Wythoff)、斐波那契博弈(Fibonacci)、尼姆博弈(Nim)、公平组合博弈(Impartial Combinatori Games)等等…

参考 https://blog.csdn.net/lgdblue/article/details/15809893

3. 组合游戏术语

  • P-positon:必败态
  • N-positon:必胜态

没有有效操作的局面为 P-position,可以转移到 P-position 的局面是 N-position,所有转移都导致形成 N-position 的局面是 P-position。

4. 引入SG函数

由于各种类型的游戏以及各种游戏的变种与组合,导致不是所有博弈都是明显的,此时引入一个有效的 NP 状态分析工具 S G SG SG 函数

首先需要定义一个 m e x ​ mex​ mex 运算, m e x ( S ) ​ mex(S)​ mex(S) 表示最小是的不属于集合 S ​ S​ S 的非负整数,比如: m e x ( { 0 , 1 , 2 , 4 } ) = 3   ,   m e x ( { 2 , 5 } ) = 0   ,   m e x ( ∅ ) = 0 ​ mex(\{0, 1, 2, 4\}) = 3~,~mex(\{2, 5\}) = 0~,~mex(\varnothing) =0​ mex({ 0,1,2,4})=3 , mex({ 2,5})=0 , mex()=0

接下来就定义 s g ( x ) = m e x { s g ( y ) ∣ y 是 x 的 后 继 } sg(x)=mex\{ sg(y) | y是x的后继 \} sg(x)=mex{ sg(y)yx},即当前局面的 s g sg sg 值等于其所有子局面 s g sg sg 值的 m e x mex mex

显然,当没有子局面的时候 s g ( x ) = 0 sg(x) = 0 sg(x)=0,此时是 P-position,必败态。那么我们就可以进一步推出当 s g ( x ) = 0 sg(x)=0 sg(x)=0 时先手必败,后手必胜。所以我们就可以利用 s g sg sg 函数判断当前局面先手是否必胜。

一般来说, s g sg sg 函数的时间复杂度是线性的,但并不是所有题目都可以通过 s g sg sg 函数来直接求解,但是可以用 s g sg sg 来分析状态来找规律!

参考 https://blog.csdn.net/bestsort/article/details/88197959

5. SG定理

任何的博弈游戏都可以抽象成一张有向无环图,即 D A G ​ DAG​ DAG,当前局面可以向子局面进行连线,显然 “叶子” 结点是必败态,是 P 点,可以转移到 P 点的是 N 点,只能转移到 N 点的是 P 点,那么这样我们就可以知道图上所有点的 NP 状态。

而博弈游戏进行抽象后可能只有一个 D A G DAG DAG,也可能有多个 D A G ​ DAG​ DAG,比如单堆石子与多堆石子的博弈游戏。对于有多个子游戏的游戏怎么处理呢?

这里有一个重要的 S G SG SG 定理:游戏和的 S G SG SG 函数等于各个游戏 s g sg sg 值的 Nim 和(即各个 s g sg sg 值进行异或的结果)。这样就可以把一个复杂的博弈问题转换成多个子问题来解决,降低了解题的难题。

6. 个人见解

看了很多的博客以及其他资料,我感觉 s g sg sg 函数跟 d f s dfs dfs 在一定程度上是类似的,在理论上 d f s dfs dfs 是万能解,而 s g sg sg 函数在理论上可以是博弈问题中的万能解,但是哪有这么好的事情,当状态数很多的时候 d f s dfs dfs s g ​ sg​ sg 函数的效率就不够了!但是我们仍然可以利用这两者进行打表,找到规律解题!

一、easy

1002:Euclid’s Game(找规律)

题意:给定两个数字 a ​ a​ a b ​ b​ b S t a n ​ Stan​ Stan O l l i e ​ Ollie​ Ollie 轮流使用大的数字减去小的数字的整数倍,问 S t a n ​ Stan​ Stan 先手能否先把其中一个数先变成 0 ​ 0​ 0

范围: a ​ a​ a b ​ b​ b 是整数。

分析:思路很简单,假设当前状态为 ( a , b ) ​ (a, b)​ (a,b),满足 a ≥ b ​ a \ge b​ ab

b = = 0 b == 0 b==0 或者 a % b = = 0 a\%b == 0 a%b==0 时必胜。

考虑到 a a a 可以减去 b b b 的整数倍,但是不能减成负数,而已知每个状态要么必胜要么必败。

如果 a a a 可以减去多次 b b b,即 a ≥ 2 b ​ a \ge 2b​ a2b,那么就相当于给了我们一个缓冲的空间,我们一定能够达到必胜态。

因为我们可以控制是自己还是对手到达 ( a % b , b ) (a\%b, b) (a%b,b) 这个状态,如果 ( a % b , b ) ​ (a\%b, b)​ (a%b,b) 是必败态,就让对手到达,否则就自己到达。

Code

#include 
using namespace std;

void solve(int a, int b, int now)
{
     
    if (a < b)
        swap(a, b);
    if (b == 0 || a >= b * 2 || a % b == 0)
    {
     
        cout << (now == 0 ? "Stan wins" : "Ollie wins") << endl;
        return;
    }
    solve(a - b, b, !now);
}

int main()
{
     
    int a, b;
    while (cin >> a >> b, a + b)
    {
     
        solve(a, b, 0);
    }
    return 0;
}

1003:Play a game(找规律)

题意:给一个 N ∗ N N*N NN 的棋盘,棋子从角落开始 8600 8600 8600 a i l y a n l u ailyanlu ailyanlu 轮流移动到水平或垂直的未访问格子里,无法移动时当前者败,问 8600 8600 8600 先手是否有必胜策略。

范围: 1 ≤ N ≤ 1 e 4 1 \le N \le 1e4 1N1e4

分析:就一个参数 N N N,其实瞎猜规律也能过了…

但是写题解还是要证明一下的。

考虑一个 N N N 为偶数的正方形,它可以被若干个 1 ∗ 2 1*2 12 的长方形完全覆盖,这样先手只要每次走长方形的另一边,保证可以走,而后手必须去寻找新的长方形,必败;否则先手需要去找长方形,必败。

参考 https://www.cnblogs.com/kuangbin/archive/2013/07/22/3204654.html

Code

#include 
using namespace std;

int n;

int main()
{
     
    while (cin >> n, n)
    {
     
        if (n % 2)
        {
     
            cout << "ailyanlu" << endl;
        }
        else
        {
     
            cout << "8600" << endl;
        }
    }
    return 0;
}

1004:Brave Game(巴什博奕)

题意:单堆石子,一共有 n ​ n​ n 个,两个人轮流取走 1 ∼ m ​ 1\sim m​ 1m 个石子,问先手是否有必胜策略,先取光所有石子。

范围: 1 ≤ n , m ≤ 1000 1 \le n, m \le 1000 1n,m1000

分析:经典的巴什博奕背景。

当剩下的石子数量为 m + 1 ​ m+1​ m+1 时必败,要把这样的局面留给对面。

当剩下石子数量为 m + 1 m+1 m+1 的倍数时同样也是必败,因为不论我们取多少石子,对面都可以把局面变成下一个 m + 1 m+1 m+1 的倍数状态。

因此只需要判断起手时石子数量 n ​ n​ n 是否正好是 m + 1 ​ m+1​ m+1 的倍数,如果是,那么无论怎么取都不能把 m + 1 ​ m+1​ m+1 的倍数个石子留给对面,如果不是,则必胜。

Code

#include 
using namespace std;

int main()
{
     
    int T;
    cin >> T;
    while (T--)
    {
     
        int n, m;
        cin >> n >> m;
        if (n % (m + 1) == 0)
        {
     
            cout << "second" << endl;
        }
        else
        {
     
            cout << "first" << endl;
        }
    }
    return 0;
}

1005:Good Luck in CET-4 Everybody!(找规律)

题意:有 n ​ n​ n 张牌, K i k i ​ Kiki​ Kiki C i c i ​ Cici​ Cici 轮流取走 2 ​ 2​ 2 的幂次数量的牌,问 K i k i ​ Kiki​ Kiki 先手是否有必胜策略先取完所有牌。

范围: 1 ≤ n ≤ 1000 1 \le n \le 1000 1n1000

分析:发现当数量为 3 3 3 的时候,对面不论怎么取都是输,当数量是 3 3 3 的倍数的时候,在对面取完之后我们可以取走数量为 1 1 1 或者 2 2 2 的牌来重新构造成 3 3 3 的倍数局面给对面,直至胜利。

因此只需要判断开局时是否面对的是 3 3 3 的就可以判断胜负。

Code

#include 
using namespace std;

int n;

int main()
{
     
    while (cin >> n)
    {
     
        if (n % 3 == 0)
        {
     
            cout << "Cici" << endl;
        }
        else
        {
     
            cout << "Kiki" << endl;
        }
    }
    return 0;
}

1006:kiki’s game(找规律)

题意:有 N ∗ M ​ N*M​ NM 的矩阵,有一个硬币放在右上角, K i k i ​ Kiki​ Kiki Z Z ​ ZZ​ ZZ 轮流当硬币移动到左边的格子、下边的格子或者左下的格子,无法移动时当前者败,问 K i k i ​ Kiki​ Kiki 先手是否有必胜策略。

范围: 1 ≤ N , M ≤ 2000 ​ 1 \le N,M \le 2000​ 1N,M2000

分析:画画图规律就很明显了,当行数和列数均为奇数时必败,否则必胜!

WHUT第九周训练整理_第1张图片
本题唯一的问题就是用 C++ 提交不管咋样都是显示 MLE,最后只能上 java 了… 下面也附了 java 的代码。

Code

#include 
using namespace std;

int main()
{
     
    int n, m;
    while (cin >> n >> m, n + m)
    {
     
        if (n % 2 && m % 2)
        {
     
            cout << "What a pity!" << endl;
        }
        else
        {
     
            cout << "Wonderful!" << endl;
        }
    }
    return 0;
}

// import java.util.Scanner;

// public class Main {
     
	
// 	public static void main(String[] args) {
     
// 		Scanner in = new Scanner(System.in);
// 		int a, b;
// 		while(in.hasNextInt()) {
     
// 			a = in.nextInt();
// 			b = in.nextInt();
// 			if(a == 0 && b == 0) break;
// 			if(a%2 == 1 && b%2 == 1) {
     
// 				System.out.println("What a pity!");
// 			}
// 			else {
     
// 				System.out.println("Wonderful!");
// 			}
// 		}
// 	}
// }

1008:邂逅明下(巴什博奕)

题意:有 n n n 个硬币,两个人轮流取走 p ∼ q ​ p \sim q​ pq 个硬币,取完者胜,问先手是否有必胜策略。

范围: n , p , q ≤ 65536 n, p, q \le 65536 n,p,q65536

分析:当 n ≤ q n \le q nq 时必胜,那么显然当 q < n ≤ p + q q < n \le p+q q<np+q 时必败。

可以继续推得以 p + q p+q p+q 为一个周期,先出现 q q q 个必胜态,再出现 p p p 个必败态,那么只要判断 n % ( p + q ) n\% (p+q) n%(p+q) 的结果是否是必胜态即可。

Code

#include 
using namespace std;

int main()
{
     
    int n, p, q;
    while (cin >> n >> p >> q)
    {
     
        if (n % (p + q) <= p && n % (p + q))
        {
     
            cout << "LOST" << endl;
        }
        else
        {
     
            cout << "WIN" << endl;
        }
    }
    return 0;
}

1013:No Gambling(找规律)

题意:给定 N N N,表示蓝方的棋盘为 N ∗ ( N + 1 ) N*(N+1) N(N+1),红方的棋盘为 ( N + 1 ) ∗ N (N+1)*N (N+1)N,下面给出的是 N = 4 N=4 N=4 的情况。双方轮流选择属于自己颜色的两个相邻的点进行连线。蓝方的目标是要从左边连到右边,红方的目标是从上方连到下方,问蓝方先手谁能赢。
WHUT第九周训练整理_第2张图片

范围: 2 ≤ N ≤ 270000 2 \le N \le 270000 2N270000

分析:这题就不多说了,随便画画你会发现怎么都输不了… 因此先手必赢。

Code

#include 
using namespace std;

int main()
{
     
    int n;
    while (cin >> n)
    {
     
        if (n == -1)
            break;
        cout << "I bet on Oregon Maple~" << endl;
    }
    return 0;
}

1014:Coin Game(找规律)

题意:有 N N N 个围成一圈的硬币,两个人轮流取走 1 ∼ k 1 \sim k 1k 个连续的硬币(取走就不形成圈了!)。问先手是否能够先取完所有硬币。

范围: 3 ≤ N ≤ 1 e 9   ,   1 ≤ k ≤ 10 ​ 3 \le N \le 1e9~,~1 \le k \le 10​ 3N1e9 , 1k10

分析:还以为取完硬币之后还是圈,导致错了好几发…

如果一次性就可以取完,那么先手必胜,否则后手必定可以将当前的硬币分成长度相同的两段,那么接下来先手只能从其中一段进行操作,后手在另一段也进行同样的操作,这样就能保证后手必胜。

而分成长度相同的两段,需要满足 k > 1 k > 1 k>1,当 k = 1 ​ k = 1​ k=1 时,根据奇偶性就可以判断胜负。

Code

#include 
using namespace std;

int main()
{
     
    int T;
    cin >> T;
    int kase = 1;
    while (T--)
    {
     
        int n, k;
        cin >> n >> k;
        cout << "Case " << kase++ << ": ";
        if (n <= k || (k == 1 && n % 2))
        {
     
            cout << "first" << endl;
        }
        else
        {
     
            cout << "second" << endl;
        }
    }
    return 0;
}

1015:悼念512汶川大地震遇难同胞——选拔志愿者(巴什博奕)

又是裸的巴什博奕,不说了。

Code

#include 
using namespace std;

int main()
{
     
    int T;
    cin >> T;
    while (T--)
    {
     
        int n, m;
        cin >> n >> m;
        if (n % (m + 1) == 0)
        {
     
            cout << "Rabbit" << endl;
        }
        else
        {
     
            cout << "Grass" << endl;
        }
    }
    return 0;
}

1016:Public Sale(巴什博奕)

题意:拍卖,底价为 0 0 0,两个人轮流开始加价 1 ∼ N 1 \sim N 1N,谁先加价到 M ​ M​ M 谁胜,问先手是否有必胜策略,如果有则输出第一次加价能出的价格。

范围: 0 < N , M < 1100 0 < N, M < 1100 0<N,M<1100

分析:还是巴什博奕,只是多了需要输出所有的答案。

可以发现如果第一次就可以全部取完,那么才可能有多种方案,否则只有一种方案,即加价到最近的 N + 1 N+1 N+1 的倍数。

Code

#include 
using namespace std;

int main()
{
     
    int m, n;
    while (cin >> m >> n)
    {
     
        if (m % (n + 1) == 0)
        {
     
            cout << "none" << endl;
        }
        else
        {
     
            if (m <= n)
            {
     
                int first = 1;
                for (int i = m; i <= n; i++)
                {
     
                    if (first)
                        first = 0;
                    else
                        cout << " ";
                    cout << i;
                }
                cout << endl;
            }
            else
            {
     
                cout << m % (n + 1) << endl;
            }
        }
    }
    return 0;
}

二、medium

1001:Calendar Game(找规律)

题意:给定一个日期(年/月/日), A ​ A​ A B ​ B​ B 轮流操作日期,可以移动到下一天,或者下个月同一天,两者都采取最优策略,问 A ​ A​ A 先手是否能够先恰好到达 2001.11.4 ​ 2001.11.4​ 2001.11.4 这一天。

范围:日期在 1900.1.1 ​ 1900.1.1​ 1900.1.1 2001.11.4 ​ 2001.11.4​ 2001.11.4 之间。

分析:通过观察发现如果当前的日期为 ( x , y ) (x, y) (x,y),可以通过一次转移到 ( x , y + 1 ) (x, y+1) (x,y+1) ( x + 1 , y ) (x+1, y) (x+1,y),奇偶性发生了变化,而目标的日期 ( 11 , 4 ) (11, 4) (11,4) 11 + 4 11+4 11+4 为奇数。

因此如果先手时所面对的日期为偶数,那么就可以转成奇数给对方,我们又可以面对偶数,最后取得胜利。

但是还要考虑细节,不是所有的日期经过转换之后奇偶性都会发生改变!

首先,月份 12 + 1 ​ 12+1​ 12+1 会变成 1 ​ 1​ 1 月,奇偶性发生改变,所以不用处理。

再考虑一般每个月要么 30 30 30 31 31 31 天, 31 → 1 31\rightarrow1 311 奇偶性发生改变,但是 30 30 30 号根据不同的月份会到 31 31 31 或者 1 ​ 1​ 1 ,前者的奇偶性改变,后者不一定。

枚举月份发现 9 9 9 月和 11 11 11 30 30 30 号的时候天数 + 1 +1 +1 之后奇偶性不变,因此虽然 ( 9 , 30 ) (9,30) (9,30) ( 11 , 30 ) (11,30) (11,30) 是奇数,但是他们一次转移后还是奇数,可以胜利。

最后是闰年的问题,只要考虑 2.29 2.29 2.29 这一天,可以转移到 ( 3 , 29 ) (3,29) (3,29) 或者 ( 3 , 1 ) (3,1) (3,1),两者都是偶数,赢不了。

Code

#include 
using namespace std;

int main()
{
     
    int T;
    cin >> T;
    while (T--)
    {
     
        int year, month, day;
        cin >> year >> month >> day;
        if ((month + day) % 2 == 0 || (day == 30 && (month == 9 || month == 11)))
        {
     
            cout << "YES" << endl;
        }
        else
        {
     
            cout << "NO" << endl;
        }
    }
    return 0;
}

1007:取石子游戏(斐波那契博弈)

题意:单堆石子,一共有 n ​ n​ n 个,两个人轮流取石子,每次取石子的数量不能超过上个人的 2 ​ 2​ 2 倍,问先手是否能先取完。

范围: 2 ≤ n ≤ 2 31 2 \le n \le 2^{31} 2n231

分析:经典斐波那契博弈。

根据 Z e c k e n d o r f Zeckendorf Zeckendorf 定理:任何正整数可以表示为若干个不连续的 F i b o n a c c i Fibonacci Fibonacci 数之和。

那么我们就可以假设剩余石子数 n = a [ 0 ] + a [ 1 ] + . . . + a [ m ] n = a[0]+a[1]+...+a[m] n=a[0]+a[1]+...+a[m],其中 a [ i ] a[i] a[i] F i b o n a c c i Fibonacci Fibonacci 数,即把这一堆石子分成 m m m 堆石子来做。

可以知道相邻的 a a a 值之间不可能是在原斐波那契序列上是连续的,比如 n = 1 + 2 n = 1+2 n=1+2,这种情况不会出现,因为 1 + 2 = 3 ​ 1+2=3​ 1+2=3,两个相邻斐波那契数之和是下一个斐波那契数。

那么得到 a [ i ] > 2 ∗ a [ i − 1 ] a[i] > 2*a[i-1] a[i]>2a[i1] (假设 a a a 是递增的)

因此我们可以从 a [ 0 ] a[0] a[0] 开始拿,对面下一次必不可能直接拿完 a [ 1 ] a[1] a[1]

那么对于每一堆石子我们都可以拿掉最后一个石子,直到游戏胜利。

因此需要满足至少有两堆石子,即石子数 n ​ n​ n 不是斐波那契数。

Code

#include 
using namespace std;

const int MAXN = 50 + 10;

long long fib[MAXN];  // 斐波那契数组

int main()
{
     
    // 预处理斐波那契,只需要50就够了
	fib[1] = fib[2] = 1;
	for (int i = 3; i <= 50; i++)
	{
     
		fib[i] = fib[i - 1] + fib[i - 2];
	}
	int n;
	while (cin >> n, n)
	{
     
		int flag = 0;
        // 判断是不是斐波那契数
		for (int i = 1; i <= 50; i++)
		{
     
			if (fib[i] == n)
			{
     
				flag = 1;
				break;
			}
		}
		if (flag)
			cout << "Second win" << endl;
		else
			cout << "First win" << endl;
	}
	return 0;
}

1009:Nim or not Nim?(sg打表找规律)

题意:有 n n n 堆石子 S S S,两个人轮流对某一堆石子取走任意数量的石子,或者把这堆石子分成两份,问先手是否有必胜策略先取完所有石子。

范围: 1 ≤ n ≤ 1 e 6   ,   1 ≤ S [ i ] ≤ 2 31 − 1 1 \le n \le 1e6~,~1\le S[i] \le 2^{31}-1 1n1e6 , 1S[i]2311

分析:在经典Nim游戏上加入一个新的规则,可以把一堆石子分成两堆,在Nim游戏中,每堆石子的 s g sg sg 值就是本身的数量,结果只要把所有石子的 s g sg sg 值进行异或就可以了。这道题目中每堆石子的 s g sg sg 值发生了改变, s g ( x ) = m e x { s g ( 0 ) , s g ( 1 ) . . . s g ( x − 1 ) , s g ( 1 ) ⊕ s g ( x − 1 ) , s g ( 2 ) ⊕ s g ( x − 2 ) . . . } sg(x) = mex\{sg(0),sg(1)...sg(x-1),sg(1)\oplus sg(x-1),sg(2) \oplus sg(x-2)...\} sg(x)=mex{ sg(0),sg(1)...sg(x1),sg(1)sg(x1),sg(2)sg(x2)...},其中 ⊕ \oplus 是异或操作,除了可以对一堆石子取走任意数量变成 0 、 1 、 2... 0、1、2... 012... 个石子,也可以分成两堆,两堆的数量可以是 ( 1 , x − 1 ) , ( 2 , x − 2 ) . . . (1, x-1),(2, x-2)... (1,x1)(2,x2)... ,分堆要把他们的 s g sg sg 值异或起来,这同样也是根据 s g sg sg 定理。

这样我们就可以根据 s g ​ sg​ sg 来打表,如下:

WHUT第九周训练整理_第3张图片

i i i 表示一堆石头的数量, s g sg sg 就是本题中单堆 i i i 个石子的 s g sg sg 值。

可以发现当 i % 4 = = 0 i\%4 == 0 i%4==0 时, s g [ i ] = i − 1 sg[i] = i-1 sg[i]=i1

i % 4 = = 3 i\%4 == 3 i%4==3 时, s g [ i ] = i + 1 ​ sg[i] = i+1​ sg[i]=i+1

其余 s t [ i ] = i st[i] = i st[i]=i

因此我们就根据这样的规律对他们的 s g sg sg 值进行异或就可以得到答案。

Code

#include 
using namespace std;

int main()
{
     
    int T;
    cin >> T;
    while (T--)
    {
     
        int n;
        cin >> n;
        int ans = 0;
        for (int i = 0; i < n; i++)
        {
     
            int x;
            cin >> x;
            if (x % 4 == 3)
                ans ^= x + 1;
            else if (x % 4 == 0)
                ans ^= x - 1;
            else
                ans ^= x;
        }
        if (ans)
            cout << "Alice" << endl;
        else
            cout << "Bob" << endl;
    }
    return 0;
}

// 下面是sg打表程序
// #include 
// using namespace std;

// const int MAXN = 1000 + 10;

// int sg[MAXN], vis[MAXN];

// int SG(int x)
// {
     
//     if (x == 0)
//         return 0;
//     if (x == 1)
//         return 1;
//     memset(vis, 0, sizeof(vis));
//     for (int i = 0; i < x; i++)
//     {
     
//         vis[sg[i]] = 1;
//     }
//     for (int i = 1; i < x; i++)
//     {
     
//         vis[sg[i] ^ sg[x - i]] = 1;
//     }
//     for (int i = 0; i < MAXN; i++)
//     {
     
//         if (!vis[i])
//             return i;
//     }
//     return 0;
// }

// int main()
// {
     
//     sg[0] = 0;
//     for (int i = 1; i < 20; i++)
//     {
     
//         sg[i] = SG(i);
//         cout << "i: " << i << " sg: " << sg[i] << endl;
//     }
//     return 0;
// }

1010:Game(阶梯博弈)

题意:有 n n n 个盒子,第 i i i 个盒子里面的卡片数量为 a r r [ i ] arr[i] arr[i] A l i c e Alice Alice B o b Bob Bob 轮流选择一个非空盒子 A A A,再选择另一个卡片数量更少的盒子 B B B,将 A A A 中任意数量的卡牌转移到 B B B 中,两个盒子需要满足 ( A + B ) % 2 = 1 (A+B)\%2 = 1 (A+B)%2=1 ( A + B ) % 3 = 0 (A+B)\%3 = 0 (A+B)%3=0。无法进行合法操作的人败。问 A l i c e Alice Alice 先手是否有必胜策略。

范围: 1 ≤ n ≤ 1 e 4   ,   a r r [ i ] ≤ 100 1 \le n \le 1e4~,~arr[i] \le 100 1n1e4 , arr[i]100

分析:这两个条件其实可以合并成一个条件,即 ( A + B ) % 6 = = 3 (A+B)\%6 == 3 (A+B)%6==3

那么就可以画出转移图,如下:

WHUT第九周训练整理_第4张图片

图片引用于 https://blog.csdn.net/qq_21048401/article/details/48140263

可以发现最后只有 1 , 3 , 4 1,3,4 1,3,4 是没有办法再去转移的,因此是终态。除此以外,发现其他 a r r [ i ] % n = 0 , 2 , 5 arr[i]\%n = 0,2,5 arr[i]%n=0,2,5 的点都是经过奇数次转移到终态,而其余的都是经过偶数次转移到终态。

这样问题就转换成阶梯Nim的博弈问题了,不懂的同学可以自行百度~

对于偶数次转移的数字 x x x,我们不需要管,它们对最后的答案是没有影响的,因为如果对手转移了 x x x 一次,我们可以再转移一次,就形成了另一个偶数次转移的数字 x ′ x' x,直到这个数字到达了终态,而此时剩下的局面就相当于消失了一个数字 x x x,相当于不存在。

那么我们只需要对奇数次转移的数字进行异或就可以了。

Code

#include 
using namespace std;

int main()
{
     
    int T;
    cin >> T;
    int kase = 1;
    while (T--)
    {
     
        int n;
        cin >> n;
        int ans = 0;
        for (int i = 1; i <= n; i++)
        {
     
            int x;
            cin >> x;
            if (i % 6 == 0 || i % 6 == 2 || i % 6 == 5)
                ans ^= x;
        }
        cout << "Case " << kase++ << ": " << (ans ? "Alice" : "Bob") << endl;
    }
    return 0;
}

1012:Alice’s Game(贪心+博弈)

题意:给 N N N X i ∗ Y i X_i*Y_i XiYi 的巧克力, A l i c e Alice Alice 只能竖着切, B o b Bob Bob 只能横着切, 不能切出更小的巧克力者败。问 A l i c e Alice Alice 先手谁会赢。

范围: 1 ≤ N ≤ 100   ,   1 ≤ X i , Y i ≤ 1 e 9 1 \le N \le 100~,~1 \le X_i,Y_i \le 1e9 1N100 , 1Xi,Yi1e9

分析:按照一般的思路来说,每一刀切下去应该让对方损失最大,而不能出现切出像 1 ∗ 5 ​ 1*5​ 15 或者 5 ∗ 1 ​ 5*1​ 51 这样的巧克力给对方,因为这样对方就可以在上面继续切 4 ​ 4​ 4 刀,而我们只能干看着。所以自然想到应该每次都对半切,这样才会让上面的情况的出现时间最晚!因为都是对半切,所以切出来的巧克力具有对称性,只需要考虑其中一块就行,即双方只对其中一块一直对半切,这样统计 N ​ N​ N 块巧克力双方能够切的最多次数,进行比较即可。

Code

#include 
using namespace std;

int main()
{
     
    int T;
    cin >> T;
    int kase = 1;
    while (T--)
    {
     
        int n;
        cin >> n;
        long long ans1 = 0, ans2 = 0;  // 注意ll
        for (int i = 0; i < n; i++)
        {
     
            int x, y;
            cin >> x >> y;
            while (x > 1 && y > 1)  // 保证都可以切
            {
     
                // 对半切,统计答案
                x /= 2;  
                y /= 2;
                ans1++;
                ans2++;
            }
            // 哪方先没法切,那么对面就可以多切
            if (x == 1)
                ans2 += y - 1;
            if (y == 1)
                ans1 += x - 1;
        }
        // 最后进行比较即可
        if (ans1 <= ans2)
        {
     
            cout << "Case " << kase++ << ": Bob" << endl;
        }
        else
        {
     
            cout << "Case " << kase++ << ": Alice" << endl;
        }
    }
    return 0;
}

1017:Being a Good Boy in Spring Festival(尼姆博弈)

题意:有 M M M 堆扑克牌,每堆牌的数量为 N i N_i Ni,两个人轮流选择其中一堆牌取走任意数量( > 1 >1 >1)的牌,问先手是否有必胜策略,有则输出第一步的方案数。

范围: 1 < M ≤ 100   ,   1 ≤ N i ≤ 1 e 6 ​ 1 < M \le 100~,~1 \le N_i \le 1e6​ 1<M100 , 1Ni1e6

分析:经典Nim博弈,就是多了一个计算方案数。

N 1 ⊕ N 2 ⊕ . . . ⊕ N M = 0 N_1 \oplus N_2 \oplus ...\oplus N_M = 0 N1N2...NM=0 时先手必败;

否则 N 1 ⊕ N 2 ⊕ . . . ⊕ N M = k N_1 \oplus N_2 \oplus ...\oplus N_M = k N1N2...NM=k,先手必胜。

此时我们需要拿牌使得异或和为 0 0 0 的局面留给对方,那我们尝试将每堆牌数 N i N_i Ni 变成 N i ⊕ k N_i \oplus k Nik,如果结果比 N i ​ N_i​ Ni 小,说明是可行的答案。

Code

#include 
using namespace std;

const int MAXN = 100 + 10;

int arr[MAXN];

int main()
{
     
    int n;
    while (cin >> n, n)
    {
     
        int ans = 0;
        for (int i = 0; i < n; i++)
        {
     
            cin >> arr[i];
            ans ^= arr[i];
        }
        int cnt = 0;
        for (int i = 0; i < n; i++)
        {
     
            if ((arr[i] ^ ans) < arr[i])
                cnt++;
        }
        if (ans == 0)
            cout << 0 << endl;
        else
            cout << cnt << endl;
    }
    return 0;
}

1018:取(m堆)石子游戏(尼姆博弈)

题意:有 M ​ M​ M 堆石子,每堆石子的数量为 N i ​ N_i​ Ni,两个人轮流选择其中一堆石子取走任意数量( > 1 ​ >1​ >1)的石子,问先手是否有必胜策略,有则输出所有可行的方案。

范围: M ≤ 2 e 5 M \le 2e5 M2e5 N i N_i Ni 是正整数

分析:跟 1017 1017 1017 差不多,稍微改一两句话即可。

Code

#include 
using namespace std;

const int MAXN = 2e5 + 10;

int arr[MAXN];

int main()
{
     
    int n;
    while (cin >> n, n)
    {
     
        int ans = 0;
        for (int i = 0; i < n; i++)
        {
     
            cin >> arr[i];
            ans ^= arr[i];
        }
        if (ans == 0)
        {
     
            cout << "No" << endl;
            continue;
        }
        cout << "Yes" << endl;
        for (int i = 0; i < n; i++)
        {
     
            if ((arr[i] ^ ans) < arr[i])
            {
     
                cout << arr[i] << " " << (arr[i] ^ ans) << endl;
            }
        }
    }
    return 0;
}

1021:A Multiplication Game(找规律)

题意:给定数字 n n n,两个人从 1 1 1 开始轮流对这个数字乘以 2 ∼ 9 2 \sim 9 29,问先手能否先得到超过 n n n 的数字。

范围: 1 < n < 4294967295 ​ 1 < n < 4294967295 ​ 1<n<4294967295

分析:打打表,发现跟边界的 2 2 2 9 9 9 有关。

n ∈ [ 2 , 9 ] n \in [2, 9] n[2,9] S t a n Stan Stan

n ∈ [ 10 , 18 ] n \in [10, 18] n[10,18] O l l i e Ollie Ollie

n ∈ [ 19 , 162 ] n \in [19,162] n[19,162] S t a n Stan Stan

n ∈ [ 163 , 324 ] n \in [163,324] n[163,324] O l l i e ​ Ollie​ Ollie

n ∈ [ 325 , 2916 ] n \in [325, 2916] n[325,2916] S t a n Stan Stan

左边界 = 上阶段右边界+1;

右边界 = 9 ∗ 2 ∗ 9 ∗ 2... 9*2*9*2... 9292...

因此我们只需要判断当前的 n n n 处于哪个阶段就可以了。

Code

#include 
using namespace std;

int main()
{
     
    long long n;
    while (cin >> n)
    {
     
        long long cnt = 0, now = 1;
        while (now < n)
        {
     
            if (cnt % 2)
                now *= 2;
            else
                now *= 9;
            cnt++;
        }
        if (cnt%2)
            cout << "Stan wins." << endl;
        else
            cout << "Ollie wins." << endl;
    }
    return 0;
}

三、hard

1011:Daizhenyang’s Coin(Mock Turtles 硬币游戏)

题意:有非常多个硬币,其中有 k k k 个正面向上,两个人轮流选择一个、二个或三个硬币进行翻转,必须满足翻转的硬币中最右边的那个原先是正面朝上的,无法进行翻转者败。问先手是否有必胜策略。

范围: 0 ≤ k i ≤ 1 e 8 0 \le k_i \le 1e8 0ki1e8

分析:经典的 Mock Turtles 硬币游戏 ,这道题目为什么是 hard 呢,因为我看了网上的题解,都跟 kuangbin 是一样的,而 kuangbin 里面说到 MT 游戏的 s g ​ sg​ sg 值与方案数是相同的,但是没有解释为什么,如果直接按照当前局面导致的子局面的 s g ​ sg​ sg 值进行计算的话是算不出来的,因为计算是递归无解的,所以我也不知道怎么打出这个 s g ​ sg​ sg 表并且找出跟二进制中 1 ​ 1​ 1 的数量的关系:设 c n t ​ cnt​ cnt x ​ x​ x 二进制串中 1 ​ 1​ 1 的数量。

s g ( x ) = { 2 ∗ x , c n t % 2 = 1 2 ∗ x + 1 , c n t % 2 = 0 ​ sg(x) = \begin{cases} 2*x,cnt\%2 = 1\\ 2*x+1 ,cnt\%2 = 0 \end{cases}​ sg(x)={ 2xcnt%2=12x+1cnt%2=0

如果有兴趣的同学可以去看看…说不定你可以看懂!

https://www.cnblogs.com/kuangbin/p/3218060.html

Code

#include 
using namespace std;

const int MAXN = 100 + 10;

int sg(int x)
{
     
    int tmp = x;
    int cnt = 0;
    while (tmp)
    {
     
        if (tmp & 1)
            cnt++;
        tmp /= 2;
    }
    if (cnt & 1)
        return 2 * x;
    else
        return 2 * x + 1;
}

int arr[MAXN];

int main()
{
     
    int n;
    while (cin >> n)
    {
     
        for (int i = 0; i < n; i++)
        {
     
            cin >> arr[i];
        }
        // 注意去重
        sort(arr, arr + n);
        n = unique(arr, arr + n) - arr;
        int sum = 0;
        for (int i = 0; i < n; i++)
        {
     
            sum ^= sg(arr[i]);
        }
        if (sum)
            cout << "No" << endl;
        else
            cout << "Yes" << endl;
    }
    return 0;
}

1019:取石子游戏(威佐夫博弈)

题意:有两堆石子,数量分别为 a a a b b b,两个人轮流从其中一对取走任意数量( > 1 >1 >1)的石子或者从两堆中同时拿走相同数量的石子,问先手是否有必胜策略先把石子全部取完。

范围: a , b ≤ 1 e 9 ​ a, b \le 1e9​ a,b1e9

分析:经典的威佐夫博弈,为什么是 h a r d ​ hard​ hard 呢?因为我看不懂…但是结论还是挺简洁的,有兴趣的同学可以去学习一下!

参考 https://www.cnblogs.com/jackge/archive/2013/04/22/3034968.html

Code

#include 
using namespace std;

int main()
{
     
	int a, b;
	while (cin >> a >> b)
	{
     
		if (a < b)
			swap(a, b);
		a = (int)((a - b) * (1 + sqrt(5)) / 2.0);
		if (a == b)
			cout << 0 << endl;
		else
			cout << 1 << endl;
	}
	return 0;
}

1020:取(2堆)石子游戏(威佐夫博弈+枚举)

题意:有两堆石子,数量分别为 a ​ a​ a b ​ b​ b,两个人轮流从其中一对取走任意数量( > 1 ​ >1​ >1)的石子或者从两堆中同时拿走相同数量的石子,问先手是否有必胜策略先把石子全部取完,有则输出所有方案。

范围: a , b ≤ 1 e 9 ​ a, b \le 1e9​ a,b1e9

分析:上面那题的加强版,要求输出所有方案时,有了结论之后只要暴力枚举就可以了。既然上面裸题都是 h a r d ​ hard​ hard 了,那这题也只能是 h a r d ​ hard​ hard 了。

参考 https://www.cnblogs.com/clliff/p/4259746.html

Code

#include 
using namespace std;

int main()
{
     
    int a, b;
    while (cin >> a >> b, a + b)
    {
     
        if (a > b)
            swap(a, b);
        double k = (1 + sqrt(5)) / 2.0;
        if (a == (int)((b - a) * k))
        {
     
            cout << 0 << endl;
        }
        else
        {
     
            cout << 1 << endl;
            for (int i = 1; i <= a; i++)
            {
     
                int x = a - i, y = b - i;
                if ((int)((y - x) * k) == x)
                {
     
                    cout << x << " " << y << endl;
                }
            }
            for (int i = b - 1; i >= 0; i--)
            {
     
                int x = a, y = i;
                if (x > y)
                    swap(x, y);
                if ((int)((y - x) * k) == x)
                {
     
                    cout << x << " " << y << endl;
                }
            }
        }
    }
    return 0;
}

1022:A simple stone game(k倍动态减法游戏)

题意:一堆石子有 n ​ n​ n 个,第一个人第一次最多拿 n − 1 ​ n-1​ n1 个,后手最多能拿先手的 k ​ k​ k 倍,问先手第一次最少要拿多少个石头能确保获胜,如果不能取胜,则输出 l o s e ​ lose​ lose

范围: 2 ≤ n ≤ 1 e 8   ,   1 ≤ k ≤ 1 e 5 2 \le n \le 1e8~,~1 \le k \le 1e5 2n1e8 , 1k1e5

分析:典型k倍动态减法游戏,具体可以查看这篇论文。

简化思路,能拿到最后一个石头就可以取得胜利,思考斐波那契博弈,我们得到的结论是如果是斐波那契数列里的数,则必输,否则必胜。原理不再赘述,在证明斐波那契博弈的时候我们用到的便是将一堆石子分成 x x x 堆(所有数字都可以变成斐波那契数列中的数字相加,且分解出的数字绝不相邻),这样我们就可以保证能拿完每一堆的最后一个。同理,该游戏也可以分解为 x x x 堆来做,那么问题就转化为如何构造这个数列。

目的:

  1. 构造一个数列使这个数列中的数字能表示所有正整数
  2. 表示正整数的时候相邻的两项必须超过k倍

那么我们可以创建一个数组 a ​ a​ a 来存放我们的答案,因为要达成目的,所以还需要另一个 b ​ b​ b 数组来保存当前能表示的数字的最大值。

很显然 a [ 0 ] = b [ 0 ] = 1 , a [ i + 1 ] = b [ i ] + 1 ​ a[0]=b[0]=1,a[i + 1] = b[i] + 1​ a[0]=b[0]=1,a[i+1]=b[i]+1,那么要保证第二个目的,我们就需要得到 b [ i ] ​ b[i]​ b[i] 的递推式。

假设 a [ j ] ∗ k < a [ i ] a[j] * k < a[i] a[j]k<a[i] ,那么我们当前能表示的最大数字就可以变为 a [ i ] + b [ j ] a[i] + b[j] a[i]+b[j],因为 b [ j ] b[j] b[j] 是从 1 − a [ j ] 1-a[j] 1a[j] 能表示的最大数字。那么就得到了这样一个式子 b [ i ] = a [ i ] + b [ j ] ( j = m a x ( { j ∣ i f a [ j ] ∗ k < a [ i ] } ) ) b[i] = a[i] + b[j] (j = max(\{j | if a[j] * k < a[i]\})) b[i]=a[i]+b[j](j=max({ jifa[j]k<a[i]})),如果不满足这个条件,那么很显然 b [ i ] = a [ i ] ​ b[i] = a[i]​ b[i]=a[i]

根据上述条件,我们就可以写出一个递推函数来进行计算,之后就是和斐波那契博弈一样处理即可。

Code

#include 
const int maxn = 10000005;
long long a[maxn], b[maxn];
int main()
{
     
	int t, n, k;
	while(~scanf("%d", &t)){
     
		for(int i = 1; i <= t; i++){
     
			scanf("%d %d", &n, &k);
			a[0] = b[0] = 1;
			int p = 0, q = 0;
			while(a[p] < n){
     
				a[p + 1] = b[p] + 1;
				p++;
				while(a[q + 1] * k < a[p]){
     
					q++;
				}
				if(a[q] * k < a[p]){
     
					b[p] = a[p] + b[q];
				} else {
     
					b[p] = a[p];
				}
			}
			printf("Case %d: ", i);
			if(n == a[p]){
     
				puts("lose");
			} else {
     
				int ans = 0;
				while(n){
     
					if(n >= a[p]){
     
						n -= a[p];
						ans = a[p];
					}
					p--;
				}
				printf("%d\n", ans);
			}
		}
	}
	return 0;
}

1023:Climbing the Hill(阶梯博弈)

题意:有 n n n 个人爬山,山顶坐标为 0 0 0,其他人 h i h_i hi 按升序给出,不同的坐标只能容纳一个人(山顶不限), A l i c e Alice Alice B o b Bob Bob 轮流选择一个人让他移动任意步,但不能越过前面的人,且不能和前面一个人在相同的位置。现在有一个人是 k i n g king king,给出 k i n g king king 是哪个人( i d id id),谁能将国王移动到山顶谁胜。

范围: 1 ≤ n ≤ 1000   ,   1 ≤ k ≤ n   ,   0 < h i < 1 e 5 1 \le n \le 1000~,~1 \le k \le n~,~0 < h_i < 1e5 1n1000 , 1kn , 0<hi<1e5

分析:先考虑简化版,没有king,谁先不能移动谁输掉。和阶梯博弈类似(blog)。根据人数的奇偶性:把人从上顶向下的位置记为 a 1 , a 2 , . . . a n a_1,a_2,...a_n a1,a2,...an,如果为偶数个人,则把 a 2 i − 1 a_{2i-1} a2i1 a 2 i a_{2i} a2i 之间的距离-1(空格数)当做一个Nim堆,变成一共 n 2 \frac {n}{2} 2n 堆的Nim游戏;如果为奇数个人,则把山顶到 a 1 a_1 a1 的距离(这是距离)当做一个Nim堆, a i ∗ 2 a_{i*2} ai2 a i ∗ 2 + 1 a_{i*2+1} ai2+1 的距离-1当做Nim堆,一共 n + 1 2 \frac {n+1}{2} 2n+1 堆,相当于往后面(山下)移动石子(从山上到山下是一个阶梯,石子向山下传递)。

考虑King的情况和上述版本几乎一致,只要把King当作普通人一样处理即可。除了两种特殊情况:1. 当King是第一个人时,Alice直接胜 2. 当King是第二个人且一共有奇数个人时,第一堆的大小需要减1。

(此处转载blog)

Code

#include
#include
#include
using namespace std;
int a[1001];
int main()
{
     
    int i,k,n,t;
    a[0]=-1;
    while(scanf("%d%d",&n,&k)!=EOF){
     
        for(i=1;i<=n;i++) scanf("%d",&a[i]);
        if(k==1){
     
            puts("Alice");
            continue;
        }
        int sg=0;
        for(i=n;i>0;i-=2)
            sg^=(t=a[i]-a[i-1]-1);
        if((n&1)&&k==2)
            sg=sg^t^(t-1);
        if(sg) puts("Alice");
        else puts("Bob");
    }
    return 0;
}

1024:A Puzzle for Pirates(海盗分金问题)

题意:有 n ​ n​ n 个海盗, m ​ m​ m 个金币,按温顺的顺序给海盗编号,使最温顺的是 1 ​ 1​ 1 号,从海盗 n ​ n​ n 开始提分配方案,如果有半数及以上海盗同意则不用被扔。从海盗 n ​ n​ n 开始提方案,问海盗 p ​ p​ p 能分到多少金币。

范围: 1 ≤ n ≤ 1 e 4   ,   1 ≤ m ≤ 1 e 7   ,   1 ≤ p ≤ n 1 \le n \le 1e4~,~1 \le m \le 1e7~,~1 \le p \le n 1n1e4 , 1m1e7 , 1pn

分析:肯定要分类讨论。

首先观察两个人的情况, 2 2 2 提出他要全部的, 2 2 2 自己本身会同意,则能拿到全部的钱。

增加了一个人的时候, 2 2 2 肯定不会赞同 3 3 3 的方案,因为如果 3 3 3 被扔了,那么 2 2 2 就能拿到所有的金币。所以只需要让 1 1 1 赞同 3 3 3 的方案即可。那么 1 1 1 肯定是 1 1 1 个金币都拿不到的,所以只需要给 1 ​ 1​ 1 一个金币,就可以了。

再增加一个人的时候,只需要让 2 2 2 赞同 4 4 4 的方案,就可以了。那么如果 2 2 2 不赞同 4 4 4 的方案,转化成 3 3 3 个人的情况, 2 2 2 一个金币都拿不到,那么只需要给 2 2 2 一个金币即可。

通过总结规律我们可以发现,当金币足够的时候,只需要让和自己同奇偶编号的人支持就行了。

那如果金币不足够的时候,例如有 10 10 10 个金币, 23 23 23 个海盗,那么 23 23 23 不管提出什么样的方案都会被扔。

增加一个海盗的时候,不管他提出怎么样的方案, 23 23 23 一定会赞同他,因为一旦不赞同,他就会被扔。所以 24 24 24 会得到一共 12 ​ 12​ 12 个人支持,那么他就能活下来。

继续增加一个海盗,前面海盗不会管这名海盗提的任何要求,所以必死

继续增加, 25 25 25 必定支持 26 26 26 提出的方案,那么一共有 12 12 12 个人支持, 26 26 26 还是必死。

继续增加, 27 27 27 会获得 13 13 13 个人支持,必死

继续增加, 28 28 28 会获得 14 14 14 个人支持,能活。

那么得到一个结论,必然会有 m m m 个人支持自己,那么还需要至少 ⌈ n 2 ⌉ − m − 1 \lceil \frac {n}{2} \rceil - m - 1 2nm1个人支持。那么设能存活的海盗的编号为 a i a_i ai,那么在他之后一部分海盗是必死的(如果没有更大的 a i + 1 a_{i+1} ai+1 出现),那么我们就可以得到这样一个式子

a i + 1 − a i = a i + 1 2 − m ​ a_{i+1}-a_{i} = \frac {a_{i+1}}{2} - m​ ai+1ai=2ai+1m,也就是 a i + 1 = 2 ∗ a i − 2 ∗ m ​ a_{i+1} = 2 * a_{i} - 2*m​ ai+1=2ai2m 只有满足这个式子,才能保证不死。通过解这个递推方程,我们可以得到一个通项公式 a i = 2 i ∗ a 0 − m ∗ ( 2 i − 2 ) ​ a_{i} = 2^i * a_0 - m*(2^i - 2)​ ai=2ia0m(2i2),又因为 a 0 = 2 ∗ m + 1 ​ a_0 = 2 * m + 1​ a0=2m+1,所以 a i = 2 i + 2 ∗ m ​ a_i = 2^i + 2*m​ ai=2i+2m,那么就得到了结论,如果 p < = a i ​ p <= a_i​ p<=ai,则不死,得到 0 ​ 0​ 0 个金币(因为后面的人不一定会给你,所有人都是处于 0 / 1 ​ 0/1​ 0/1 的状态),否则,必死。

那么总结如下:

  1. 海盗是金币的两倍及以下

    那第一个提出分配方案的必会被半数以上的人通过。所以如果 p = n ​ p=n​ p=n 答案为 m − n − 1 2 ​ m - \frac{n-1}{2}​ m2n1,其他人如果和最后一个人奇偶性相同,则得到一个金币。

  2. 其他情况

    如果 p < = a i p <= a_i p<=ai,则不死,得到 0 0 0 个金币(因为后面的人不一定会给你,所有人都是处于 0 / 1 ​ 0/1​ 0/1 的状态),否则,必死。

Code

#include 
int main()
{
     
	int t, n, m, p;
	while(~scanf("%d", &t)){
     
		while(t--){
     
			scanf("%d %d %d", &n, &m, &p);
			if(n > 2 * m + 1){
     
				int tmp = n - (m << 1), tp = 1;
				while(tp <= tmp){
     
					tp <<= 1;
				}
				tp >>= 1;
				if(2 * m + tp >= p){
     
					puts("0");
				} else {
     
					puts("Thrown");
				}
			} else {
     
				if(p == n){
     
					printf("%d\n", m - (n - 1) / 2);
				} else {
     
					if(!((p & 1) ^ (n & 1))){
     
						puts("1");
					} else {
     
						puts("0");
					}
				}
			}
		}
	}
	return 0;
} 

1025:Switch lights(nim积)

题意:有 n n n 个灯亮着,每个灯的位置为 ( x i , y i ) (x_i, y_i) (xi,yi),每次可以选择 4 4 4 个角(可以是一个灯)把灯的状态翻转,而且右下角的灯原先必须亮着。当无法操作时,游戏结束。问先手能否必胜。

范围: n ≤ 1000   ,   1 ≤ x i , y i ≤ 1 e 4 n \le 1000~,~1 \le x_i,y_i \le 1e4 n1000 , 1xi,yi1e4

分析:nim积模板题,具体的证明可以查看这篇论文的4.4.4章节。

Code

#include
int m[2][2] = {
     0, 0, 0, 1};
int Nim_Mult_Power(int x,int y)
{
     
	if(x<2)
		return m[x][y];
	int a=0;
	for(;;a++)
		if(x>=(1<<(1<<a))&&x<(1<<(1<<(a+1))))
			break;	
	int m=1<<(1<<a);	
	int p=x/m,s=y/m,t=y%m;	
	int d1=Nim_Mult_Power(p,s);	
	int d2=Nim_Mult_Power(p,t);	
	return (m*(d1^d2))^Nim_Mult_Power(m/2,d1);
}
int Nim_Mult(int x,int y)
{
     
	if(x<y)
		return Nim_Mult(y,x);
	if(x<2)
		return m[x][y];	
	int a=0;
	for(;;a++)
		if(x>=(1<<(1<<a))&&x<(1<<(1<<(a+1))))
			break;	
	int m=1<<(1<<a);
	int p=x/m,q=x%m,s=y/m,t=y%m;
	int c1=Nim_Mult(p,s),c2=Nim_Mult(p,t)^Nim_Mult(q,s),c3=Nim_Mult(q,t);	
	return (m*(c1^c2))^c3^Nim_Mult_Power(m/2,c1);
}
int main()
{
     
	int t, n, x, y;
	while(~scanf("%d", &t)){
     
		while(t--){
     
			scanf("%d", &n);
			int ans = 0;
			for(int i = 0; i < n; i++){
     
				scanf("%d %d", &x, &y);
				ans ^= Nim_Mult(x, y);
			}
			if(ans){
     
				puts("Have a try, lxhgww.");
			} else {
     
				puts("Don't waste your time.");
			}
		}
	}
	return 0;
}

【END】感谢观看!

你可能感兴趣的:(竞赛整理分享,acm,博弈论,whut)