前 k 轮已经能区分所有 x,当且仅当前缀询问的 lcm 等于 lcm(1..n),因此答案就是所有必需质数最高幂第一次被覆盖轮次的最大值。
OJ: luogu
题目 ID: P8980
难度:提高+/省选-
标签:数论最大公约数质因数分解推导
日期: 2026-06-21 00:29
题意
你先选一个 x (1<=x<=n)。
之后第 i 轮会被问到一个 a_i,你要回答 gcd(x,a_i)。
如果小朋友根据前若干轮答案已经能唯一确定 x,游戏就在那一轮结束。
要求你把 x 选得尽量“难猜”,使游戏结束得最晚;若一直无法唯一确定,则输出 game won't stop。
思路
先看一个可以直接验证想法的朴素解:
#include <bits/stdc++.h>
using namespace std;
// brute.cpp:小数据暴力解。
// 直接枚举 x,再模拟每轮后还有哪些候选数与它回答完全一致。
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int n, Q;
cin >> n >> Q;
vector<int> a(Q + 1);
for (int i = 1; i <= Q; i++) {
cin >> a[i];
}
bool never_stop = false;
int best = 1;
for (int x = 1; x <= n; x++) {
vector<int> cand;
for (int y = 1; y <= n; y++) {
cand.push_back(y);
}
int stop_round = Q + 1;
for (int i = 1; i <= Q; i++) {
int gx = gcd(x, a[i]);
vector<int> next_cand;
for (int y : cand) {
if (gcd(y, a[i]) == gx) {
next_cand.push_back(y);
}
}
cand.swap(next_cand);
if ((int) cand.size() == 1) {
stop_round = i;
break;
}
}
if (stop_round == Q + 1) {
never_stop = true;
break;
}
best = max(best, stop_round);
}
if (never_stop) {
cout << "game won't stop\n";
} else {
cout << best << '\n';
}
}
return 0;
}brute.cpp 会直接枚举 x,并维护当前还有哪些候选数和它回答一致,只适合小数据。
正式解的核心结论是:
前
k轮已经能区分1..n中所有数
当且仅当
lcm(a_1,...,a_k) = lcm(1,2,...,n)。
原因在于 gcd 只能观察质因子指数,而且会被询问数 a_i 的质因子指数截断。
如果某个必需的最高幂 p^e 从未出现在前缀询问的 lcm 中,那么“是否拥有这层最高幂”就无法被看出来,至少会留下两个不同的候选数。
反过来,只要所有必需的最高幂都被覆盖了,那么每个数的质因数分解信息都能被完全识别,所有 x 也就都被区分开了。
于是答案就很直接:
- 对每个必需质数
p - 记录它第一次达到所需指数的轮次
- 最后取这些轮次的最大值
如果有某个必需质数直到最后也没被覆盖,答案就是:
game won't stop
代码
#include <bits/stdc++.h>
using namespace std;
const int HARD_N = 32465962;
struct FastScanner {
static const int BUFSIZE = 1 << 20;
int idx, size;
char buf[BUFSIZE];
FastScanner() {
idx = 0;
size = 0;
}
inline char getch() {
if (idx >= size) {
size = (int) fread(buf, 1, BUFSIZE, stdin);
idx = 0;
if (size == 0) {
return 0;
}
}
return buf[idx++];
}
template <class T>
bool read_unsigned(T &x) {
x = 0;
char ch = getch();
while (ch != 0 && (ch < '0' || ch > '9')) {
ch = getch();
}
if (ch == 0) {
return false;
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getch();
}
return true;
}
} scanner;
int spf[HARD_N + 1];
vector<int> primes;
int first_pos[HARD_N + 1];
void build_sieve() {
primes.reserve(2100000);
for (int i = 2; i <= HARD_N; i++) {
if (spf[i] == 0) {
spf[i] = i;
primes.push_back(i);
}
for (int j = 0; j < (int) primes.size(); j++) {
int p = primes[j];
long long v = 1LL * i * p;
if (v > HARD_N) {
break;
}
spf[v] = p;
if (p == spf[i]) {
break;
}
}
}
}
int need_exp_for_prime(int p, int n) {
int exp = 1;
long long cur = p;
while (cur * p <= n) {
cur *= p;
exp++;
}
return exp;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
build_sieve();
unsigned int T;
scanner.read_unsigned(T);
vector<int> touched;
touched.reserve(100000);
for (unsigned int tc = 0; tc < T; tc++) {
unsigned long long n64;
unsigned int Q;
scanner.read_unsigned(n64);
scanner.read_unsigned(Q);
// n=1 时,小朋友第一轮后一定已经能唯一确定。
if (n64 == 1) {
unsigned long long tmp;
for (unsigned int i = 0; i < Q; i++) {
scanner.read_unsigned(tmp);
}
cout << 1 << '\n';
continue;
}
bool impossible = false;
// 对于 p > sqrt(n) 的质数,所需指数只能是 1。
// 每个 a_i <= n 最多只能包含一个这样的质数。
// 因此如果这类质数个数已经大于 Q,则不可能全部覆盖。
if (n64 > HARD_N) {
impossible = true;
}
int n = 0;
if (!impossible) {
n = (int) n64;
int large_prime_count =
(int) (upper_bound(primes.begin(), primes.end(), n) -
upper_bound(primes.begin(), primes.end(), (int) sqrt((long double) n)));
if (large_prime_count > (int) Q) {
impossible = true;
}
}
for (unsigned int i = 1; i <= Q; i++) {
unsigned long long a64;
scanner.read_unsigned(a64);
if (impossible) {
continue;
}
int x = (int) a64;
while (x > 1) {
int p = spf[x];
int cnt = 0;
while (x % p == 0) {
x /= p;
cnt++;
}
int need = need_exp_for_prime(p, n);
if (cnt >= need && first_pos[p] == 0) {
first_pos[p] = (int) i;
touched.push_back(p);
}
}
}
if (impossible) {
cout << "game won't stop\n";
continue;
}
int ans = 1;
int limit = (int) (upper_bound(primes.begin(), primes.end(), n) - primes.begin());
for (int i = 0; i < limit; i++) {
int p = primes[i];
if (first_pos[p] == 0) {
impossible = true;
break;
}
if (first_pos[p] > ans) {
ans = first_pos[p];
}
}
if (impossible) {
cout << "game won't stop\n";
} else {
cout << ans << '\n';
}
for (int p : touched) {
first_pos[p] = 0;
}
touched.clear();
}
return 0;
}复杂度
预处理一次筛表后,主过程只需要对所有询问做质因数分解。
整体复杂度由分解总量决定,可以通过本题数据。
总结
这题表面上在找一个“最难猜的 x”,其实根本不需要枚举 x。
真正要判断的是:
- 前缀询问的
lcm是否已经覆盖了1..n所需的全部质数最高幂
一旦想到这一点,整题就变成了一个很简洁的数论覆盖问题。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
