题目
【题目描述】
克里特岛以野人群居而著称。岛上有排列成环行的M个山洞。这些山洞顺时针编号为1,2,…,M。岛上住着N个野人,一开始依次住在山洞C1,C2,…,CN中,以后每年,第i个野人会沿顺时针向前走Pi个洞住下来。每个野人i有一个寿命值Li,即生存的年数。下面四幅图描述了一个有6个山洞,住有三个野人的岛上前四年的情况。三个野人初始的洞穴编号依次为1,2,3;每年要走过的洞穴数依次为3,7,2;寿命值依次为4,3,1。
奇怪的是,虽然野人有很多,但没有任何两个野人在有生之年处在同一个山洞中,使得小岛一直保持和平与宁静,这让科学家们很是惊奇。他们想知道,至少有多少个山洞,才能维持岛上的和平呢?
【输入】
输入文件的第1行为一个整数N(1<=N<=15),即野人的数目。第2行到第N+1每行为三个整数Ci, Pi, Li (1<=Ci,Pi<=100, 0<=Li<=10^6 ),表示每个野人所住的初始洞穴编号,每年走过的洞穴数及寿命值。
【输出】
输出文件仅包含一个数M,即最少可能的山洞数。输入数据保证有解,且M不大于10^6。
【样例输出】
3
1 3 4
2 7 3
3 2 1
【样例输出】
6
【提示】
该样例对应于题目描述中的例子。
题解
看完题目后,我忍不住吐槽一句——题目好烂!
题目中说野人群居,但是却又让野人单独居住,这还叫群居吗?!
好了,回归正题。这题一看就知道是一道数学题(废话),直接暴力是肯定不行的(废话)。
可以先枚举m,再枚举所有两个野人的情况,看看他们会不会在有生之年相遇在同一个山洞。
前面的枚举很简单,关键在于如何判断。
设野人 i 和野人 j 在第 x 年相遇,那么可以列出同余方程:
C
i
+
P
i
⋅
x
≡
C
j
+
P
j
⋅
x
(
m
o
d
m
)
C
i
+
P
i
⋅
x
=
C
j
+
P
j
⋅
x
+
m
y
(转化)
P
i
⋅
x
−
P
j
⋅
x
=
m
y
+
C
j
−
C
i
(移项)
(
P
i
−
P
j
)
x
−
m
y
=
C
j
−
C
i
(化简)
\begin{aligned} C_i+P_i\cdot x & \equiv C_j+P_j\cdot x & \text{$(mod\space m)$}\\ C_i+P_i\cdot x & =C_j+P_j\cdot x+my & \text{(转化)}\\ P_i\cdot x-P_j\cdot x & =my+C_j-C_i & \text{(移项)}\\ (P_i-P_j)x-my & =Cj-Ci & \text{(化简)} \end{aligned}
Ci+Pi⋅xCi+Pi⋅xPi⋅x−Pj⋅x(Pi−Pj)x−my≡Cj+Pj⋅x=Cj+Pj⋅x+my=my+Cj−Ci=Cj−Ci(mod m)(转化)(移项)(化简)
设
a
=
P
i
−
P
j
a=P_i-P_j
a=Pi−Pj,
b
=
−
m
b=-m
b=−m,
c
=
C
j
−
C
i
c=C_j-C_i
c=Cj−Ci,就可以把方程转化成以下形式:
a
x
+
b
y
=
c
\begin{aligned} ax+by=c \end{aligned}
ax+by=c
怎么样?眼熟吧!这就是扩展GCD!
首先,可以在式子两边同时模 c g c d ( a , b ) \cfrac{c}{gcd(a,b)} gcd(a,b)c(如果c不能整除gcd(a,b),那么方程无整数解,可直接退出),为什么?因为a和b一定是可以整除gcd(a,b)的,所以ax和by也一定可以整除它,ax+by也必定可以。
所以式子就变成了这个样子:
a
x
+
b
y
=
g
c
d
(
a
,
b
)
∵
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
m
o
d
b
)
∴
a
x
+
b
y
=
b
x
+
(
a
m
o
d
b
)
y
\begin{aligned} ax+by&=gcd(a,b)\\ \because gcd(a,b)&=gcd(b,a\mod b)\\ \therefore ax+by&=bx+(a\mod b)y\\ \end{aligned}
ax+by∵gcd(a,b)∴ax+by=gcd(a,b)=gcd(b,amodb)=bx+(amodb)y
所以我们可以逐步递归下去,直到b=0(看下文)。
继续转换,得
a
x
+
b
y
=
b
x
+
(
a
m
o
d
b
)
y
=
b
x
+
(
a
−
[
a
b
]
b
)
y
=
b
x
+
a
y
−
[
a
b
]
b
y
=
a
y
+
b
(
x
−
[
a
b
]
y
)
\begin{aligned} ax+by&=bx+(a\mod b)y\\ &=bx+(a-[\frac{a}{b}]b)y\\ &=bx+ay-[\frac{a}{b}]by\\ &=ay+b(x-[\frac{a}{b}]y) \end{aligned}
ax+by=bx+(amodb)y=bx+(a−[ba]b)y=bx+ay−[ba]by=ay+b(x−[ba]y)
其中,[x]表示下取整x
由此,得
e
x
g
c
d
(
a
x
+
b
y
)
=
e
x
g
c
d
(
a
y
+
b
(
x
−
[
a
b
]
y
)
)
exgcd(ax+by)=exgcd(ay+b(x-[\frac{a}{b}]y))
exgcd(ax+by)=exgcd(ay+b(x−[ba]y))
然后就可以这样递归下去了。
现在再来谈谈b=0的情况:
当b=0时,式子变成了
a
x
=
g
c
d
(
a
,
b
)
ax=gcd(a,b)
ax=gcd(a,b)
显然
a
=
g
c
d
(
a
,
b
)
a=gcd(a,b)
a=gcd(a,b)(因为x是整数),即x=1,y=0
以下是扩展GCD的代码:
int exgcd(int a,int b)
{
if(b==0)
{
x=1,y=0;
return a;
}
int d=exgcd(b,a%b),t;
t=x;x=y;y=t-a/b*y;
return d;
}
注意:扩展GCD的返回值是gcd(a,b)
由于x可能不是非负的最小解,因此我们要把它处理一下
x=x*cc/d%(b/d);
if(x<0) x+=abs(b/d);
前一句可以让x尽可能地靠近0,后一句可以让x非负。
最后判断一下两个野人可不可以在有生之年相遇,即看看是否
x
≤
min
(
l
i
,
l
j
)
x\le \min(l_i,l_j)
x≤min(li,lj)
这题就搞定了。
代码
#include<cstdio>
using namespace std;
int c[20],p[20],l[20],x,y;
inline int min(int x,int y){return x<y?x:y;}
inline int max(int x,int y){return x>y?x:y;}
inline int abs(int x){return x<0?0-x:x;}
int exgcd(int a,int b)
{
if(b==0)
{
x=1,y=0;
return a;
}
int d=exgcd(b,a%b),t;
t=x;x=y;y=t-a/b*y;
return d;
}
int main()
{
int n,m=0,i,j,k,d,a,b,cc;
bool bk;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d%d%d",&c[i],&p[i],&l[i]);
m=max(m,c[i]);
}
for(k=m;k<=1e+6;k++)
{
bk=0;
for(i=1;i<n;i++)
{
for(j=i+1;j<=n;j++)
{
a=p[i]-p[j],b=k,cc=c[j]-c[i];
d=exgcd(a,b);
if(cc%d) continue;
x=x*cc/d%(b/d);
if(x<0) x+=abs(b/d);
if(x<=min(l[i],l[j]))
{
bk=1;
break;
}
}
if(bk) break;
}
if(!bk) break;
}
printf("%d\n",k);
return 0;
}