题目链接
题目描述
给定一个包含 个不同整数的集合
。Alice 和 Bob 轮流进行操作,Alice 先手。每次操作,玩家需选择集合
中两个不同的整数
和
。
- 若
的结果也在集合
中,则执行此次操作的玩家立即输掉游戏。
- 若
的结果不在集合
中,则将
添加到集合
中,轮到对方操作。
假设双方都采取最优策略,判断谁将获胜。
解题思路
这是一个典型的公平博弈问题。直接模拟整个游戏过程非常复杂,因此我们需要寻找问题的核心性质或不变量,来简化游戏模型。
-
寻找不变量 (Invariant): 设
是当前集合
中所有数字的最大公约数 (GCD)。如果我们从集合中选择两个数
和
,我们知道
一定能整除
和
。根据数论的基本性质,
也必然能整除它们的差
。 这意味着,当我们计算出一个新的数字
并将其加入集合时,新集合所有数字的 GCD 仍然是
。 因此,集合中所有数字的最大公约数在整个游戏过程中是一个不变量。
-
简化游戏模型: 既然 GCD (
) 是不变的,那么在游戏中出现的所有数字都必须是初始 GCD 的倍数。设初始集合中的最大值为
。任何新产生的数
必然小于
。 这启发我们将游戏过程看作是“填空”:初始集合提供了一些
的倍数,而游戏的终点是将从
到
之间所有
的倍数全部“填满”。
-
确定终局状态: 游戏在什么时候结束?当集合
对于“取差的绝对值”这个操作变得“封闭”时,游戏结束。此时,对于任意
,
的结果都已经在
中,导致当前玩家的任何操作都会直接判负。 可以证明,这个最终的“封闭”集合就是
,其中
。
-
计算总移动次数: 我们已经知道了游戏的初始状态和终局状态,因此可以精确地计算出从开始到结束总共有多少次有效的移动。
- 终局状态的集合大小为
。
- 初始状态的集合大小为
。
- 因此,游戏中总共可以执行的有效移动次数为
。
- 终局状态的集合大小为
-
判断胜负: 问题被转化成了一个极其简单的 impartial game:总共有
个石子,Alice 和 Bob 轮流取一个,取走最后一个石子的人获胜。
- 如果总移动次数
是奇数,先手 Alice 会执行第 1, 3, ..., T 步操作。Alice 会执行最后一次操作,因此 Alice 获胜。
- 如果总移动次数
是偶数,后手 Bob 会执行第 2, 4, ..., T 步操作。Bob 会执行最后一次操作,因此 Bob 获胜。
- 如果总移动次数
算法步骤:
- 计算初始集合所有数字的最大公约数
。
- 找到初始集合中的最大值
。
- 计算总移动次数
。
- 根据
的奇偶性判断胜负。
代码
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
using namespace std;
// 用于计算最大公约数的辅助函数
long long gcd(long long a, long long b) {
return b == 0 ? a : gcd(b, a % b);
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n;
cin >> n;
vector<long long> a(n);
long long max_val = 0;
for (int i = 0; i < n; ++i) {
cin >> a[i];
if (a[i] > max_val) {
max_val = a[i];
}
}
if (n == 0) {
cout << "Bob" << endl;
return 0;
}
long long common_divisor = a[0];
for (int i = 1; i < n; ++i) {
common_divisor = gcd(common_divisor, a[i]);
}
long long total_moves = (max_val / common_divisor) - n;
if (total_moves % 2 != 0) {
cout << "Alice" << endl;
} else {
cout << "Bob" << endl;
}
return 0;
}
import java.util.Scanner;
public class Main {
// 用于计算最大公约数的辅助函数
private static long gcd(long a, long b) {
return b == 0 ? a : gcd(b, a % b);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
long[] a = new long[n];
long maxVal = 0;
for (int i = 0; i < n; i++) {
a[i] = sc.nextLong();
if (a[i] > maxVal) {
maxVal = a[i];
}
}
if (n == 0) {
System.out.println("Bob");
return;
}
long commonDivisor = a[0];
for (int i = 1; i < n; i++) {
commonDivisor = gcd(commonDivisor, a[i]);
}
long totalMoves = (maxVal / commonDivisor) - n;
if (totalMoves % 2 != 0) {
System.out.println("Alice");
} else {
System.out.println("Bob");
}
}
}
import sys
import math
def gcd_list(numbers):
if not numbers:
return 0
result = numbers[0]
for i in range(1, len(numbers)):
result = math.gcd(result, numbers[i])
return result
def solve():
n = int(input())
a = list(map(int, input().split()))
if n == 0:
print("Bob")
return
max_val = max(a)
common_divisor = gcd_list(a)
total_moves = (max_val // common_divisor) - n
if total_moves % 2 != 0:
print("Alice")
else:
print("Bob")
solve()
算法及复杂度
- 算法: 数论 (最大公约数)
- 时间复杂度: 我们需要遍历一次数组以找到最大值,时间复杂度为
。接着,我们需要计算
个数字的最大公约数,这可以通过迭代计算实现,时间复杂度为
。因此,总时间复杂度为
。
- 空间复杂度: 我们需要一个数组来存储
个初始数字,因此空间复杂度为
。