Skip to content

洛谷 - P2421 荒岛野人

检测到 KaTeX 加载失败,可能会导致文中的数学公式无法正常渲染。

题面

题目描述

克里特岛以野人群居而著称。岛上有排列成环行的 mm 个山洞。这些山洞顺时针编号为 1,2,,m1, 2, \dots, m。岛上住着 nn 个野人,一开始依次住在山洞 C1,C2,,CnC_1, C_2, \dots, C_n中,以后每年,第 ii 个野人会沿顺时针向前走 PiP_i 个洞住下来。

每个野人 ii 有一个寿命值 LiL_i,即生存的年数。

下面四幅图描述了一个有 66 个山洞,住有三个野人的岛上前四年的情况。三个野人初始的洞穴编号依次为 1,2,31, 2, 3;每年要走过的洞穴数依次为 3,7,23, 7, 2;寿命值依次为 4,3,14, 3, 1

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

输入格式

11 行为一个整数 nn,即野人的数目。

22 行到第 n+1n + 1 每行为三个整数 Ci,Pi,LiC_i, P_i, L_i,表示每个野人所住的初始洞穴编号,每年走过的洞穴数及寿命值。

输出格式

仅包含一个数 mm,即最少可能的山洞数。输入数据保证有解,且 mm 不大于 10610^6

输入输出样例

样例输入 #1

3
1 3 4
2 7 3
3 2 1

样例输出 #1

6

数据范围与约定

对于 100%100\% 的数据,1n151 \leq n \leq 151Ci,Pi1001\leq C_i, P_i \leq 1000Li1060\leq L_i\leq 10^6,最终答案 m106m \leq 10^6

思路

iji \ne ji,j[1,n]i, j \in [1, n] 时,第 ii 个野人和第 jj 个野人在同一个山洞中当且仅当情况如下:

Ci+xPiCj+xPj(modm)(1)\tag{1} C_i + x P_i \equiv C_j + x P_j \pmod m

那么只需要使其在 [0,min(Li,Lj)][0, \min(L_i, L_j)] 范围内无解即可维持岛上的和平。

移项,有:

x(PiPj)CjCi(modm)(2)\tag{2} x (P_i - P_j) \equiv C_j - C_i \pmod m

那么可以使用扩展欧几里得算法求出 (2)(2) 式的解:

(PiPj)x+my=CjCi(3)\tag{3} (P_i - P_j) x + my = C_j - C_i

再观察数据范围,发现答案 m106m \leq 10^6,那么可以考虑在 [max1inCi,106][\max_{1 \leq i \leq n} C_i, 10^6] 区间内枚举答案,复杂度为 O(mn2logCi)O(m n^2 \log C_i),可以通过本题。

题外话:今天下午打模拟赛的时候这道题的 (3)(3) 式推错了没切掉,真是可惜啊。

注:本题答案不具有单调性,不能二分。

代码

#include <algorithm>
#include <iostream>
#include <tuple>

using std::cin;
using std::cout;
const char endl = '\n';

const int N = 20;

int n, max, ans;
std::tuple<int, int, int> a[N];

int exgcd(int a, int b, int& x, int& y) {
    if (!b) {
        x = 1, y = 0;
        return a;
    }

    int g = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return g;
}

bool check(int k) {
    for (int i = 1; i < n; i++) {
        for (int j = i + 1; j <= n; j++) {
            int c1, p1, l1, c2, p2, l2;
            std::tie(c1, p1, l1) = a[i];
            std::tie(c2, p2, l2) = a[j];

            int a = p1 - p2,
                b = k,
                c = c2 - c1;

            int x, y,
                g = exgcd(a, b, x, y);

            if (c % g) continue;

            // 此处 g 可能是负数,因此需要取绝对值
            a /= g, b = std::abs(b / g), c /= g;
            x = (x * c % b + b) % b;

            if (x <= std::min(l1, l2)) return false;
        }
    }

    return true;
}

int main() {
    std::ios::sync_with_stdio(false);

    cin >> n;

    for (int i = 1, c, p, l; i <= n; i++) {
        cin >> std::get<0>(a[i]) >> std::get<1>(a[i]) >> std::get<2>(a[i]);

        max = std::max(max, std::get<0>(a[i]));
    }

    for (int i = max; i <= 1000000; i++) {
        if (check(i)) {
            cout << i << endl;

            exit(0);
        }
    }

    return 0;
}

感谢 JohnSonloy 指出本文中的错误,已修正。