「DROI」Round 1 游戏

GitHub跳转原题关系图返回列表

前 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

思路

先看一个可以直接验证想法的朴素解:

cpp
#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

代码

cpp
#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 所需的全部质数最高幂

一旦想到这一点,整题就变成了一个很简洁的数论覆盖问题。

一图流解析

这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

一图流解析