原题链接:https://www.luogu.com.cn/problem/P2421
难度:提高+/省选-
涉及知识点:扩展欧几里得算法(exgcd)
题意
有 n n n 只野人,第 i i i 只野人初始所在洞穴为 c [ i ] c[i] c[i],每年走过的洞穴数量为 p [ i ] p[i] p[i],寿命值为 l [ i ] l[i] l[i] 年。求在每个洞穴始终不会有两只及以上的野人同时出现在某一年的情况下,至少需要的洞穴数 M M M。
分析与解决
在有
m
m
m 个洞穴时,对于第
i
i
i 只野人,第
x
x
x 年所在的洞穴编号应为
(
c
[
i
]
+
x
×
p
[
i
]
)
m
o
d
m
(c[i]+x\times p[i])\bmod m
(c[i]+x×p[i])modm,且需要保证对于不同的两只野人,该式的值不会相同。那么对于这
n
n
n 只野人,两两建立如下方程,考虑是否有解。如果在有
m
m
m 个洞穴时两两建立的
n
2
n^2
n2 个方程都有解,则
m
m
m 个洞穴不够,还需要增加;如果都无解,则说明
m
m
m 个洞穴可行。
c
[
i
]
+
x
×
p
[
i
]
≡
c
[
j
]
+
x
×
p
[
j
]
(
m
o
d
m
)
c[i]+x\times p[i]\equiv c[j]+x\times p[j](\bmod \ m)
c[i]+x×p[i]≡c[j]+x×p[j](mod m)然后再做如下推导:
由同余式变换规则得,
x
×
p
[
i
]
−
x
×
p
[
j
]
≡
c
[
j
]
−
c
[
i
]
(
m
o
d
m
)
x\times p[i]-x\times p[j]\equiv c[j]-c[i](\bmod \ m)
x×p[i]−x×p[j]≡c[j]−c[i](mod m)
由分配律得,
x
×
(
p
[
i
]
−
p
[
j
]
)
≡
c
[
j
]
−
c
[
i
]
(
m
o
d
m
)
x\times (p[i]-p[j])\equiv c[j]-c[i](\bmod \ m)
x×(p[i]−p[j])≡c[j]−c[i](mod m)
整理得,
x
×
(
p
[
i
]
−
p
[
j
]
)
−
m
y
=
c
[
j
]
−
c
[
i
]
x\times (p[i]-p[j])-my=c[j]-c[i]
x×(p[i]−p[j])−my=c[j]−c[i]
所以可以直接套用扩展欧几里得算法,如果在有
m
m
m 个洞穴时,求
x
x
x 的最小解,如果
x
≤
min
(
l
[
i
]
,
l
[
j
]
)
x\leq \min(l[i],l[j])
x≤min(l[i],l[j]),说明在有
m
m
m 个洞穴,当前的
x
x
x 是一个解,因为所有野人都活着,那就是有解的情况,而我们要求的是无解的情况,因而排除。
输入数据时记录一下最大的初始洞穴编号,即在不考虑冲突的情况最少需要的洞穴数。然后从该值开始增加,每次 check 一下 i i i 个洞穴是否可行即可。
AC代码
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 20;
int n, m, ans;
ll x, y;
int C[N], P[N], L[N];
ll exgcd (ll a, ll b, ll &x, ll &y)
{
if (!b)
{
x = 1, y = 0;
return a;
}
int d = exgcd (b, a % b, y, x);
y -= a / b * x;
return d;
}
bool check (int m)
{
for (int i = 1; i <= n; i++)
{
for (int j = i + 1; j <= n; j++)
{
ll a = P[i] - P[j], b = m, c = C[j] - C[i];
int d = exgcd (a, b, x, y);
if (c % d) continue;
a /= d, b /= d, c /= d;
if (b < 0) b = -b; //纠正负数
x = (x * c % b + b) % b;
if (x <= L[i] && x <= L[j]) return false; //年份x在他们的寿命范围内,有解,不行
}
}
return true;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> C[i] >> P[i] >> L[i];
m = max(m, C[i]);
}
for (int i = m;; i++)
{
if (check(i))
{
cout << i << endl;
return 0;
}
}
return 0;
}