P2421 [NOI2002] 荒岛野人

题目描述:


克里特岛以野人群居而著称。岛上有排列成环形的 \(m\) 个山洞。这些山洞顺时针编号为 \(1,2,\dots ,m\)。岛上住着 \(n\) 个野人,一开始依次住在山洞 \(C_1,C_2,\dots ,C_n\) 中,以后每年,第 \(i\) 个野人会沿顺时针向前走 \(P_i\) 个洞住下来。
每个野人 \(i\) 有一个寿命值 \(L_i\),即生存的年数。
下面四幅图描述了一个有 \(6\) 个山洞,住有三个野人的岛上前四年的情况。三个野人初始的洞穴编号依次为 \(1,2,3\);每年要走过的洞穴数依次为 \(3,7,2\);寿命值依次为 \(4,3,1\)。

奇怪的是,虽然野人有很多,但没有任何两个野人在有生之年处在同一个山洞中,使得小岛一直保持和平与宁静,这让科学家们很是惊奇。他们想知道,至少有多少个山洞,才能维持岛上的和平呢?

初步分析


针对于上面描述的问题,初步理解下来就是每个野人会有\((C_i,P_i,L_i)[i=1,\cdots,N]\)三个参数分别对应着自己的初始位置,每次前进的步数,存活时间,第 \(i\)个野人会在存活期间会走过\((C_i+k\times P_i)\pmod{M},[k=0,\cdots,L_i]\),我们要寻找一个最小的\(M\)使得没有同一时间存在至少两个野人上述等式结果一致。

数论分析


接下来就是从数论角度进行分析:

  • 已知 \(M\in[max\{C_i\},10^6]\)内一定有解,这个时间复杂度如果能以大概 \(O(M)\)的时间复杂度解决问题,是可以满足 \(OI\)的审核要求的。

    \(Remark: M\)的左边界是因为洞穴数一定是大于等于最大的初始编号数的。

  • 假设我们现在从小到大遍历到了一个准确的 \(M\),怎么去判断它是否满足要求?我们需要反向看待这一件事情,假设有 \(i\)和 \(j\)两名野人在其存活期间能走到同一个洞穴,这就意味着:

    \[C_i+k\times P_i\equiv C_j+k\times P_j\pmod{M}\rightarrow k\times (P_i-P_j)\equiv C_j-C_i\pmod{M} \]

    我们令 \(A=P_i-P_j\),\(B=C_j-C_i\),即意味着同余方程 \(A\times k\equiv B\pmod{M}\rightarrow A\times k+M\times y=B\)存在解。

  • 如果只是存在解,但该解并不在 \((i,j)\)野人的存活时间内,两者其实并没有相遇,我们要确定上述同余方程的最小正整数解是否满足 \(k_{min}\leq min\{L_i,L_j\}\)。如若满足,则意味着该 \(M\)下第 \(i\)个野人和第 \(j\)个野人会出现同住一个洞穴的情况,已经可以排除了;反之,我们就可以确保该 \(M\)下两者一定不会相遇,进而可以考虑下一组 \((i,j)\)了。因此,遍历 \(M\)的内层循环的时间复杂度就是 \(O(N^2)\),\(N\in[1,15]\),时间复杂度是完全可以接受的。

  • 至此,我们找到了时间复杂度为 \(O(M\times N^2)\)的解决方案:

#include <iostream>
#include <vector>

int exgcd(int a, int b, int &x, int &y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main() {
    int N = 0;
    scanf("%d", &N);
    std::vector<int> C(N + 1, 0);
    std::vector<int> P(N + 1, 0);
    std::vector<int> L(N + 1, 0);
    int maxC = 0;
    for (int i = 1; i <= N; i++) {
        scanf("%d %d %d", &C[i], &P[i], &L[i]);
        maxC = std::max(maxC, C[i]);
    }
    int M = maxC;
    for (; M <= 1000000; M++) {
        bool valid = true;
        for (int i = 1; i <= N && valid; i++) {
            for (int j = i + 1; j <= N && valid; j++) {
                int A = P[i] - P[j];
                int B = C[j] - C[i];
                int x, y;
                // 保证求解的 d 是正数
                if (A < 0) {
                    A = -A;
                    B = -B;
                }
                int d = exgcd(A, M, x, y);
                if (B % d == 0) {
                    int step = M / d;
                    // 特解
                    x = 1LL * x * (B / d) % step;
                    x = (step + x) % step;
                    if (x <= std::min(L[i], L[j])) valid = false;
                }
            }
        }
        if (valid) {
            printf("%d\n", M);
            break;
        }
    }
    return 0;
}
  • 时间复杂度:\(O(M+N^2)\)
  • 空间复杂度:\(O(3N)\)
posted @ 2026-09-09 17:12  chen_xing  阅读(8)  评论(0)    收藏  举报