POJ 1061(exgcd)

这题是exgcd……

我居然连wa了2天……

至少知道DIV函数的性质了   (-x) div t =-(x div t)    

                           (-x) div (-t) = (x div t)

发现自己数论蒟蒻……


Program p1061;
var
   x,y,n,m,l,t:int64;
   a,b,c,c2:int64;
function exgcd(a,b:int64):int64;
var
   x1,y1:longint;
begin
   if b=0 then
   begin
      x:=1;
      y:=0;
      exit(a);
   end;
   exgcd:=exgcd(b,a mod b);
   x1:=y;
   y1:=x-(a div b)*y;
   x:=x1;
   y:=y1;
end;
begin

   read(x,y,m,n,l);
   a:=n-m;  
   c:=x-y;  
   b:=l;   
   c2:=exgcd(a,b);
   if (c mod c2<>0) then writeln('Impossible')
   else
   begin
      x:=x*(c div c2);
      y:=y*(c div c2);
      t:=b div c2;


      if t<0 then
         while (x<0) do
         begin

            x:=x-t*abs(x div t)-t;
         end;
      if t>0 then while (x<0) do x:=x+t*abs(x div t)+t;

      x:=x mod (b div c2);
      writeln(x);
   end;


end.


<think>嗯,这个问看起来是关于两只青蛙跳跃相遇的。我得仔细读目,理解条件。目大意是说,两只青蛙在同一条纬度线上,朝同一个方向(西)跳,纬度线是首尾相接的数轴,也就是环形。它们的起点分别是x和y,每次跳的距离是m和n米,每次跳的时间相同,问它们什么时候能碰到,或者不可能。 首先,我得把问转化为数学问。假设它们跳了t次后相遇,那么它们的位置应该满足某种条件。因为是环形,所以位置要考虑模L的情况。 青蛙A的位置在t次跳跃后的位置应该是x + m*t。青蛙B的位置是y + n*t。但由于是环形,实际的位置需要取模L。相遇的条件应该是这两个位置的差是L的整数倍,或者说它们的相对位置差是L的倍数。 不过因为方向相同,所以如果它们的速度不同的话,可能会有相遇的可能。或者可能永远追不上。例如,如果A比B跳得远,但初始位置在B后面,可能需要绕一圈才能追上。或者如果它们的速度差导致无法相遇,那就Impossible。 具体来说,我们需要找到t使得:(x + m*t)(y + n*t) mod L。也就是(m - n)*t ≡ (y - x) mod L。或者写成同余方程:(m - n)*t ≡ (y - x) (mod L)。这里的t必须是非负整数。 这个问可以转化为求解线性同余方程a*t ≡ b mod L,其中a = m - n,b = y - x。不过要注意,这里的a可能为负数,需要处理。比如,如果a是负数,我们可以将方程两边取相反数,转化为正数的a。另外,如果a=0的话,这时候要看b是否也是0,否则无解。 所以,这个问现在转化为求线性同余方程的解。根据数论中的知识,线性同余方程ax ≡ b mod m有解的条件是gcd(a, m)必须整除b。如果满足这个条件,那么解的形式是x ≡ x0 mod (m/d),其中d是gcd(a, m),x0是某个特解。否则无解。 这里我们的方程是a*t ≡ b mod L。所以需要计算d = gcd(a, L),然后看d是否整除b。如果不行,则输出Impossible。否则,我们可以用扩展欧几里得算法来求出一个解t0,然后找到最小的正整数解。 需要注意的是,这里t必须是非负的,并且可能需要调整到最小的解。例如,当得到解t0后,通解是t0 + k*(L/d),其中k是整数。我们需要找到最小的非负解。 那具体步骤应该是这样的: 1. 将问转化为同余方程:(m - n)*t ≡ (y - x) mod L。 2. 计算a = m - n,b = y - x。 3. 处理符号问:比如,如果a是负数,可以把方程两边同时乘以-1,得到正数的a,但需要调整b的符号。或者,直接在计算时处理。 4. 计算d = gcd(a, L)。如果d不整除b,则无解。 5. 否则,用扩展欧几里得算法求出方程a*t + L*k = b的整数解t0,其中k是另一个变量。 6. 根据扩展欧几里得的结果,找到t的通解,然后找到最小的正解。 不过这里可能需要特别注意模运算的处理。例如,在求解过程中,可能需要将解调整到合适的范围。比如,t0可能是一个负数解,需要通过加上(L/d)的倍数来得到正解。 此外,需要注意,当a=0时,也就是m=n的情况下,此时只有当x ≡ y mod L时,才会相遇,此时t=0。否则,如果x和y初始位置不同,且速度相同,那么它们永远无法相遇。这时候,如果初始位置已经相同(即x ≡ y mod L),则输出0次;否则,不可能。 那么具体来说,处理步骤如下: 首先,处理方程: (m - n) * t ≡ (y - x) mod L 即求t使得该等式成立。我们需要找到最小的非负整数t。 令a = m - n,b = y - x,模数为L。所以方程变为a * t ≡ b mod L。 当a=0时: 如果b ≡ 0 mod L,那么t可以是0;否则,无解。 当a≠0时: 我们需要求解线性同余方程。这时候,需要计算d = gcd(a, L)。如果d不整除b,则无解。否则,可以通过扩展欧几里得算法找到解。 假设d | b,那么方程的解是t ≡ (b/d) * inv(a/d, L/d) mod (L/d)。其中inv表示模逆元。 或者,通过扩展欧几里得算法求出x和y,使得a*x + L*y = d,然后乘以(b/d)得到特解。 这里可能有点复杂,我需要回忆扩展欧几里得算法如何应用。 假设我们有方程ax + by = gcd(a,b)。扩展欧几里得算法可以求出x和y。在我们的情况,方程是a*t + L*k = b。这里的系数可能不是gcd,所以需要调整。 实际上,正确的步骤应该是: 方程a*t ≡ b mod L → a*t - L*k = b,其中k是整数。这可以转化为求解a*t - L*k = b的整数解t和k。根据贝祖定理,这个方程有解当且仅当gcd(a, L) | b。假设d = gcd(a, L),则必须d | b才能有解。 此时,我们可以将方程两边除以d,得到 (a/d)*t - (L/d)*k = b/d。然后,我们可以找到方程(a/d)*t + (-k)*(L/d) = b/d的解。因为a/d和L/d是互质的,所以此时可以通过扩展欧几里得算法求出解。 例如,假设我们求出一个解t0,那么所有解的形式是t = t0 + k*(L/d),其中k是整数。我们需要找到最小的非负t。 所以,步骤总结: 1. 计算a = m - n,b = y - x。 2. 如果a == 0: - 如果b mod L == 0,则说明初始位置相同,输出0。 - 否则,输出Impossible。 3. 否则: - 计算d = gcd(a, L)。 - 如果b % d !=0,则无解,输出Impossible。 - 否则,方程转化为:(a/d)*t ≡ (b/d) mod (L/d)。因为此时a/d和L/d互质,所以可以求逆元。 - 求出a/d的逆元inv,模数是L/d。这样t ≡ (b/d)*inv mod (L/d)。最小的正解即为这个结果,再调整到非负数。 但是如何用扩展欧几里得算法求这个解? 可能需要具体的例子来理解。比如,假设a=3,L=12,b=6。那么d=3。这时候方程是3t ≡6 mod12。等价于t≡2 mod4。所以解是t=2+4k,最小正解是2。 或者,比如样例输入是1 2 3 4 5,这可能不是正确的样例。但看问中的Sample Input给出的是“1 2 3 4 5”,而Sample Output是4。这可能对应某个具体的输入,比如可能输入的x=1,y=2,m=3,n=4,L=5。但需要具体分析。 比如,原来的输入可能应该是x=1,y=2,m=3,n=4,L=5。那么a = m -n = -1。b= y-x=1。方程是-1 *t ≡1 mod5 →等价于t ≡-1 mod5 →t ≡4 mod5。所以最小的正解是4次,即输出4。 这说明在处理a为负数的时候,可能需要将方程两边乘以-1,得到正数的系数。例如,原方程是a*t ≡b mod L,其中a是负数。此时可以转化为等价方程 (-a)*t ≡ -b mod L,然后继续处理。这样可以让a变为正数,方便计算gcd。 或者,在计算gcd的时候,可以取绝对值。因为gcd(a, L)等于gcd(|a|, L)。这可能更方便。 因此,在处理a的时候,应该取其绝对值。例如: d = gcd(|a|, L) 或者,在计算时,将a和b调整到正确的符号。 比如,原方程是a*t ≡ b mod L。如果a是负数,我们可以将方程改写为 (-a)*t ≡ -b mod L,此时新的a是正数,新的b是 -b。这样求解可能更简单。 或者,保持原方程不变,但在计算gcd的时候取绝对值。这样,在扩展欧几里得算法中,无论a的正负,都可以正确计算。 可能更简单的处理方式是,将a和b都除以它们的符号,使得a变为正数。比如,当a为负数时,方程等价于 (-a)*t ≡-b mod L。这时候,新的a是正数,新的b是 -b。然后继续处理。 例如,假设原方程是-3t ≡ 2 mod7。可以转换为3t ≡-2 mod7 →3t ≡5 mod7。然后解这个方程。 所以,在程序中,当a是负数时,我们可以让a = -a,b = -b,这样方程就变为正数的a。这可能更便于后续处理。 所以,处理步骤的调整: 在得到a = m -n,b = y -x之后: 如果a <0,那么将a = -a,b = -b。因为原方程等价于 (-a)*t ≡ (-b) mod L → a*t ≡b mod L(但原式是a*t ≡b mod L,现在可能需要注意符号)。 或者,原方程是a*t ≡b mod L。如果a是负数,那么将两边乘以-1,得到新的方程(-a)*t ≡-b mod L。这时候新的a是正的,新的b是 -b。这相当于将原方程转换为正数a的情况。 例如,原方程是:-5*t ≡3 mod 10 →5t ≡-3 mod10 →5t ≡7 mod10。此时,a=5,b=7,L=10。 所以,在程序中,可以这样处理: 当a <0时: a = -a; b = -b; 然后,继续处理。这时候,原方程变为a*t ≡b mod L。这不会改变解的情况,因为原方程等价于(a*t -b)≡0 mod L,调整后的方程是(a*t + (-b))≡0 mod L。这可能需要注意符号,但正确的处理方式是,将方程转化为正系数的形式。 这样处理之后,后续的求解步骤可以统一处理a为正的情况。 接下来,当a=0的情况已经处理过了。否则,计算d = gcd(a, L)。如果b不能被d整除,则无解。否则,我们可以将方程两边除以d,得到新的方程: (a/d)*t ≡ (b/d) mod (L/d) 因为此时a/d和L/d互质,所以存在逆元。我们需要求逆元。 所以,步骤: 计算d = gcd(a, L) 如果 (b % d) !=0 →Impossible 否则: a' = a/d L' = L/d b' = b/d 求a'的逆元inv,使得 a' * inv ≡1 mod L' 则t0 = (b' * inv) mod L' 这时候,解为t0,但可能得到的是负数。需要取模得到最小的正数。 然后,我们需要找到最小的非负t,满足这个解。例如,t0可能是一个负数,这时候要加上L'的倍数,使其变为正数。 例如,假设t0 = -5,L'=10 →-5 mod10=5 →正确解是5. 所以,在程序中,应该计算t0 = (b' * inv) % L' 然后,这个t0就是最小的非负解。 但是需要注意的是,当inv是负数的时候,可能导致t0为负数。所以在取模时,要确保得到的是正余数。 所以,现在问转化为如何求a'的逆元。这可以通过扩展欧几里得算法实现,或者用其他方法。 扩展欧几里得算法可以求出x和y,使得a'*x + L'*y = 1。因为a'和L'互质,所以存在这样的x和y。这里的x就是逆元。 所以,我们可以通过扩展欧几里得算法找到x的值,然后将x mod L',以确保其是正数。 现在,我们需要编写扩展欧几里得算法的函数,返回gcd,以及x和y,使得a*x + b*y = gcd。 例如,函数exgcd(a, b)返回d, x, y,其中d是gcd(a,b),x和y是系数。这样,当我们需要解a'x ≡1 mod L'时,可以调用exgcd(a', L'),得到x的值,然后调整到模L'的正数范围。 现在,将这些步骤综合起来,可以得到算法的大致流程: 输入x, y, m, n, L. 计算a = m -n b = y -x 处理a的符号: 如果a <0 →a = -a,b = -b 处理模数L: 因为方程是mod L,所以当处理后的a是0的时候,判断b是否也是0 mod L。如果是,输出0;否则Impossible. 当a ==0的情况: if (b % L ==0) →输出0 else →Impossible 否则: d = gcd(a, L) if (b % d !=0) →Impossible else: a' = a/d L' = L/d b' = b/d 求解a' * inv ≡1 mod L' inv = mod_inverse(a', L') →可以用扩展欧几里得求得 t0 = (b' * inv) % L' 此时,t0是一个解,但可能不是最小的正解? 或者,可能已经是正确的解? 例如,在样例输入中: 假设输入是1 2 3 4 5。那么x=1,y=2,m=3,n=4,L=5. 那么a=3-4=-1 →处理为a=1,b =2-1=1 →变成b=-1= -1 ? 原方程是: (3-4)*t ≡ (2-1) mod5 →-1*t ≡1 mod5 →等价于1*t ≡-1 mod5 →t≡4 mod5。所以解是4,这是正确的。 这里处理后的a=1,b= -1。但原方程转换后的形式是a*t ≡b mod L →1*t ≡-1 mod5。即t≡4 mod5。解是4. 此时,d=gcd(1,5)=1。因为-1%1=0,所以可以解。 然后,a'=1/1=1,L'=5/1=5,b'= (-1)/1= -1. 求inv是1的逆元,即1的逆元是1 mod5. t0= (-1)*1 mod5 →等于4 mod5 →4。正确。 所以,t0=4是正确的解。 所以,这个步骤是正确的。 另一个例子,假设x=0,y=0,m=1,n=1,L=10。那么初始位置相同,所以输出0次。 如果x=0,y=5,m=2,n=3,L=10。此时a=2-3=-1 →处理后a=1,b=5-0=5 →因为处理后b变为-5 →方程是1*t ≡-5 mod10 →即t≡5 mod10。所以解是5次。但是在这个情况下,青蛙A的位置每次是0+2*5=10→0 mod10。青蛙B的位置是5+3*5=20→0 mod10。确实相遇。 所以,步骤是正确的。 现在,如何编写代码? 首先,处理输入,读入x,y,m,n,L. 计算a = m -n b = y -x 处理a的符号: 如果a <0: a = -a b = -b 当a ==0时: if b mod L ==0 →输出0 else →输出Impossible 否则: 计算d = gcd(a, L) if b %d !=0 →输出Impossible else: a' = a/d L' = L/d b' = b/d 使用扩展欧几里得算法求x,使得a'*x ≡1 mod L' inv = x mod L' →确保是正数 t0 = (b' * inv) % L' 输出t0 但需要注意,求逆元的时候,必须a'和L'互质。因为此时a'和L'是互质的,所以逆元存在。 所以,问中的关键是如何用扩展欧几里得算法求逆元。 扩展欧几里得算法实现: 例如,函数exgcd(a, b)返回d, x, y。其中d是gcd(a,b),x和y满足a*x + b*y =d. 当a和b互质时,d=1,所以x是a的逆元模b。因此,对于我们的问,当我们需要求inv是a'的逆元模L',因为a'和L'互质,所以exgcd(a', L')得到的x就是逆元。不过需要注意的是,x可能为负数,所以需要调整到模L'的正数范围内。 例如,假设a'=3,L'=7。exgcd(3,7)返回1, -2, 1。因为3*(-2) +7*1=1。所以逆元是-2 mod7=5. 所以在代码中,inv = (x % L' + L') % L' →确保是正数。 所以,编写exgcd函数: 在C语言中,可以用递归或者迭代的方式实现。递归可能更容易实现。 例如: int exgcd(int a, int b, long long *x, long long *y) { if (b ==0) { *x =1; *y=0; return a; } else { int d = exgcd(b, a%b, y, x); //交换x和y,因为a = b, b = a%b. *y -= a/b * (*x); return d; } } 注意,这里参数是int,但目中的数值可能很大,需要用long long类型。所以可能需要用long long来处理参数,避免溢出。 例如,在本的输入中,x和y可以是20亿,L可以是21亿。所以在处理过程中,中间结果可能会超过int的范围,必须用long long。 所以,在代码中,所有变量应该用long long类型。 例如,输入x, y, m, n, L都是long long类型。 exgcd函数应该处理long long参数。 所以,修改后的exgcd函数: long long exgcd(long long a, long long b, long long *x, long long *y) { if (b ==0) { *x =1; *y=0; return a; } else { long long d = exgcd(b, a % b, y, x); *y -= a / b * (*x); return d; } } 这个函数返回的是gcd(a,b),并设置x和y的值。 现在,当我们需要求逆元的时候,调用exgcd(a', L', &x, &y)。其中,x是a'的逆元模L'吗? 是的。因为 a'*x + L'*y =1 →模L'的话,得到a'*x ≡1 mod L'。所以x就是逆元。 因此,在代码中,在求解inv的时候: 调用exgcd(a_prime, L_prime, &x, &y),其中a_prime = a/d,L_prime = L/d. 得到的x就是逆元。然后,将x mod L_prime,以确保是正数。 例如,如果x是负数,则x = x % L_prime + L_prime,然后再次mod L_prime。 或者,可以用inv = (x % L_prime + L_prime) % L_prime; 这样得到的就是最小的正整数解。 因此,在代码中: long long a = m -n; long long b = y -x; 处理a的符号: if (a <0) { a = -a; b = -b; } if (a ==0) { // 处理初始情况 if (b % L ==0) { printf("0\n"); } else { printf("Impossible\n"); } } else { long long d = exgcd(a, L, &x, &y); //这里的x和y是exgcd的解参数,可能不需要用到? // 正确的做法是调用exgcd(a, L, &x0, &y0),得到d= gcd(a,L) // 然后检查是否d divides b. if (b %d !=0) { printf("Impossible\n"); } else { long long a_prime = a /d; long long L_prime = L /d; long long b_prime = b /d; // 求a_prime 的逆元 mod L_prime. long long inv, tmp; exgcd(a_prime, L_prime, &inv, &tmp); inv = (inv % L_prime + L_prime) % L_prime; long long t0 = (b_prime * inv) % L_prime; // 此时t0是一个可能的解,但需要确保非负? // 因为模运算的结果可能为0到L_prime-1. printf("%lld\n", t0); } } 但这里可能存在一些问,比如,当调用exgcd(a_prime, L_prime, &inv, &tmp)时,得到的inv是否是正确的逆元? 是的,因为此时a_prime和L_prime是互质的。所以exgcd返回的d=1,而inv是a_prime的逆元模L_prime. 所以,这样得到的t0就是解吗? 是的。例如,在样例输入中: 假设输入是x=1,y=2,m=3,n=4,L=5. 处理后的a = m-n=3-4=-1 →变为a=1,b= y-x=2-1=1 →变为b=-1. 进入else块: d=exgcd(1,5, &inv, &tmp) →d=1. 此时inv=1,tmp=0. 因为1*1 +5*0=1. 所以,inv=1. 然后,a_prime=1/1=1. L_prime=5/1=5. b_prime= (-1)/1= -1. inv= (1 %5 +5) %5=1. t0= (-1 *1) mod5 →-1 mod5=4. 所以,输出4,正确。 另一个例子,比如a=3,b=6,L=12,d=3. 则处理后的a_prime=1,L_prime=4,b_prime=2. 求inv是1 mod4的逆元,即1. t0=2*1 mod4=2. 正确。 那如果方程是3t ≡6 mod12 →等价于t≡2 mod4 →解是2,6,10,… 但这里的解是t0=2,是正确的。 因此,代码的处理是正确的。 但是,为什么在求解的时候,不需要考虑通解?例如,当通解是t0 +k*(L_prime)时,为什么直接输出t0? 因为此时方程的通解是t ≡t0 mod L_prime,所以t的最小非负解是t0 % L_prime,即已经处理过了。 例如,在之前的例子中,t0=4是正确的解。在其他情况下,比如t0= L_prime-1,那么是最大的解,但可能更小的? 不,因为模运算后的结果已经是0到L_prime-1之间的数,所以取模后的结果就是最小的非负解。 例如,当解是t0= L_prime*2 +3,取模得到3,所以输出3次。 因此,代码是正确的。 现在,需要处理所有的边界情况: 情况一:a=0的情况。此时,若b≡0 mod L,则输出0。否则,Impossible. 情况二:a≠0,但无法整除b →Impossible. 情况三:解存在,找到最小的非负解。 另外,必须注意数据类型的范围。输入中的x、y、m、n、L都可以达到2e9,所以在计算过程中,变量应该使用long long类型,避免溢出。 例如,在代码中,所有变量应声明为long long。输入时用%lld格式读取。 此外,在扩展欧几里得算法中,中间计算可能会溢出吗?例如,当a和b是很大的数值时,递归可能导致栈溢出?或者,在C语言中,可能要用非递归实现,但通常递归深度是O(log n),所以对于2e9的数,递归深度不会太大,应该没问。 综上,C语言代码的大致结构是: 读取x,y,m,n,L. 计算a = m -n,b= y -x. 处理a的符号: 如果a<0 →a=-a,b=-b. 如果a ==0: if (b %L ==0) →输出0. else →输出Impossible. 否则: 计算d = gcd(a, L). 如果b %d !=0 →输出Impossible. 否则: a_prime = a/d; L_prime = L/d; b_prime = b/d; 调用exgcd(a_prime, L_prime, &inv, &temp). inv = (inv % L_prime + L_prime) % L_prime; t0 = (b_prime * inv) % L_prime; 输出t0. 这里的gcd计算需要正确,因为C语言标准库中没有gcd函数,需要自己实现。或者,可以用exgcd来求gcd,或者单独写一个函数。 或者,在exgcd函数中,返回的d就是gcd(a,b)。所以,当求gcd(a,L)的时候,可以用exgcd(a, L, &x, &y) →返回的d就是gcd(a,L). 但是,在代码中,我们可能不需要x和y的值,只需要d的值。或者,在计算d的时候,可以调用exgcd函数,传入临时变量x和y。 例如: long long x0, y0; long long d = exgcd(a, L, &x0, &y0); 这样,d就是gcd(a, L). 是的。 综上,C语言代码如下: #include <stdio.h> long long exgcd(long long a, long long b, long long *x, long long *y) { if (b == 0) { *x = 1; *y = 0; return a; } else { long long d = exgcd(b, a % b, y, x); *y -= a / b * (*x); return d; } } int main() { long long x, y, m, n, L; scanf("%lld%lld%lld%lld%lld", &x, &y, &m, &n, &L); long long a = m - n; long long b = y - x; // 处理a的符号 if (a < 0) { a = -a; b = -b; } if (a == 0) { // 青蛙速度相同 if (b % L == 0) { printf("0\n"); } else { printf("Impossible\n"); } return 0; } // 求gcd(a, L) long long x0, y0; long long d = exgcd(a, L, &x0, &y0); if (b % d != 0) { printf("Impossible\n"); } else { long long a_prime = a / d; long long L_prime = L / d; long long b_prime = b / d; // 求a_prime的逆元 mod L_prime long long inv, tmp; exgcd(a_prime, L_prime, &inv, &tmp); inv = (inv % L_prime + L_prime) % L_prime; long long t0 = (b_prime * inv) % L_prime; printf("%lld\n", t0); } return 0; } 但是,在代码中,当调用exgcd(a, L, &x0, &y0)的时候,我们得到的是方程a*x0 + L*y0 =d的解。这可能与后续求a_prime的逆元无关?或者,这里是否正确? 不,当进入else块的时候,我们已经处理了a的符号,并且得到了d = gcd(a, L)。此时,我们需要求的是方程a_prime * t ≡ b_prime mod L_prime,其中a_prime和L_prime互质。 所以,正确的做法是,在else块中,求exgcd(a_prime, L_prime, &inv, &tmp),因为此时a_prime和L_prime互质,所以inv是a_prime的逆元模L_prime. 但是在代码中,在计算d的时候,我们调用了exgcd(a, L, ...),得到d=gcd(a,L)。然后,在else块中,已经将a_prime = a/d, L_prime=L/d。所以,此时a_prime和L_prime互质,所以它们的gcd是1. 因此,调用exgcd(a_prime, L_prime, &inv, &tmp)是正确的,得到的是inv是a_prime的逆元模L_prime. 例如,在样例输入中: a=1,L=5. d = gcd(1,5)=1. a_prime=1/1=1. L_prime=5/1=5. exgcd(1,5, &inv, &tmp) →返回1,inv=1,tmp=0. 所以,inv=1 mod5=1. 这样,t0 = (b_prime)*inv mod L_prime. 其中,b_prime = b/d = (-1)/1= -1. 所以,-1 mod5=4. 所以 t0=4*1 mod5=4. 输出4,正确。 因此,代码是正确的。 但有一个疑问:在else块中,当处理完a和b之后,调用exgcd(a, L, &x0, &y0)来求gcd(a, L),此时得到的x0和y0可能并不用于后续计算,对吗?是的。后续的逆元计算是针对a_prime和L_prime的,所以必须重新调用exgcd(a_prime, L_prime, ...). 这可能会导致两次调用exgcd,但这是必须的。 或者,是否可以找到一种方式,将所有的计算都通过一次调用完成? 例如,在第一次调用exgcd(a, L, x0, y0)时,得到方程a*x0 + L*y0 =d. 其中,d是gcd(a,L). 此时,方程a*x0 ≡d mod L → (a/d)*x0 ≡1 mod (L/d). 所以,x0是(a/d)的逆元模(L/d)吗? 是的。因为方程a*x0 + L*y0 =d →两边除以d得到: (a/d)*x0 + (L/d)*y0 =1. 这说明 (a/d)*x0 ≡1 mod (L/d). 所以,x0就是(a/d)的逆元模(L/d). 因此,在代码中,是否可以利用第一次调用得到的x0来直接作为逆元? 例如,当d=gcd(a, L)时,且我们处理后的方程是a_prime*t ≡b_prime mod L_prime →其中 a_prime =a/d, L_prime =L/d. 则,此时a_prime的逆元是x0 mod L_prime. 因为x0是满足 a/d *x0 ≡1 mod L/d →是的。 因此,在代码中,可以省去第二次调用exgcd,直接使用第一次得到的x0,并调整其符号。 这可能更高效,因为不需要两次调用exgcd. 例如,在第一次调用exgcd(a, L, &x0, &y0)得到d后,x0是方程a*x0 +L*y0 =d的解。此时,除以d得到: a/d *x0 + L/d *y0 =1. 所以,模L/d的话,a/d *x0 ≡1 mod L/d. 这说明x0是a/d的逆元 mod L/d. 因此,此时inv = x0 mod L/d. 所以,可以省去第二次调用exgcd,而直接使用x0的值。 那么,在代码中: long long d = exgcd(a, L, &x0, &y0); 然后,在else块中: long long L_prime = L/d; long long inv = (x0 % L_prime + L_prime) % L_prime; 这样,inv就是a_prime的逆元,无需第二次调用exgcd. 这显然更高效,也更简单。 比如,在样例输入中: 第一次调用exgcd(1,5, &x0, &y0)得到d=1, x0=1,y0=0. inv =1 mod5 →1. 同样正确。 另一个例子,假设a=3,L=12,d=3. 调用exgcd(3,12, &x0, &y0) →得到d=3。方程3x0 +12y0=3 →可能x0=1,y0=0 →x0=1. a/d=1, L/d=4. 此时,x0=1 →inv=1 mod4=1. 所以,这样是正确的。 另一个例子,假设a=5, L=3,则d=1. 调用exgcd(5,3, &x0, &y0) →5和3的exgcd返回1,x0=-1, y0=2. 所以,inv = (-1) mod3=2. 此时,a/d=5, L/d=3 →5*x ≡1 mod3. 5 mod3=2 →2x≡1 mod3 →x=2. 所以,inv是2,而根据x0=-1 mod3=2,是正确的. 因此,这样处理是正确的。 因此,代码可以优化为: 在else块中: 不需要调用exgcd两次,直接使用第一次得到的x0,调整到mod L_prime. 因此,修改后的代码如下: 在else块中: // 计算d = exgcd(a, L, &x0, &y0); 已经调用过 long long L_prime = L / d; // inv是x0 mod L_prime long long inv = (x0 % L_prime + L_prime) % L_prime; long long t0 = ( (b/d) * inv ) % L_prime; 这样,就可以省去第二次调用exgcd的步骤。 这是更优的解决方案。因此,可以修改代码: 修改后的代码: ... else { long long x0, y0; long long d = exgcd(a, L, &x0, &y0); if (b % d !=0) { printf("Impossible\n"); } else { long long L_prime = L / d; // x0是方程 a*x0 + L*y0 =d 的解。这里需要求a/d的逆元,即 x0 mod L_prime. long long inv = (x0 % L_prime + L_prime) % L_prime; long long t0 = ( (b / d) * inv ) % L_prime; printf("%lld\n", t0); } } ... 这样,是否正确? 例如,在样例输入中: d=1. b= -1 →b/d= -1. inv =x0=1 mod5=1. t0 = (-1 *1) mod5=4. 正确。 另一个例子,假设a=5,L=3,b=2. 原方程是5t ≡2 mod3 →2t ≡2 mod3 →t≡1 mod3/ gcd(2,3)=1. d=1. 调用exgcd(5,3, &x0, &y0) →x0=-1. inv = (-1) mod3=2. b/d=2/1=2. t0=2*2 mod3=4 mod3=1 →正确解是1. 是的。 因此,修改后的代码是正确的,并且更高效。 因此,最终的代码应如下: #include <stdio.h> long long exgcd(long long a, long long b, long long *x, long long *y) { if (b == 0) { *x = 1; *y = 0; return a; } else { long long d = exgcd(b, a % b, y, x); *y -= a / b * (*x); return d; } } int main() { long long x, y, m, n, L; scanf("%lld%lld%lld%lld%lld", &x, &y, &m, &n, &L); long long a = m - n; long long b = y - x; // 处理a的符号 if (a < 0) { a = -a; b = -b; } if (a == 0) { // 青蛙速度相同 if (b % L == 0) { printf("0\n"); } else { printf("Impossible\n"); } return 0; } long long x0, y0; long long d = exgcd(a, L, &x0, &y0); if (b % d != 0) { printf("Impossible\n"); } else { long long L_prime = L / d; long long inv = (x0 % L_prime + L_prime) % L_prime; long long t0 = ( (b / d) * inv ) % L_prime; printf("%lld\n", t0); } return 0; } 这样,代码就正确了。测试样例输入: 假设输入是: 1 2 3 4 5 →样例输入? 根据问中的Sample Input部分,用户可能给出的输入如: Sample Input 1 2 3 4 5 Sample Output 4 按照代码,得到的结果是正确的。 另一个测试案例: x=0,y=0,m=1,n=1,L=10 →a=0,b=0 →输出0。 另一个测试案例: x=0,y=5,m=2,n=3,L=10 →a=2-3=-1 →a=1,b=5 →处理后b= -5. 方程是1*t ≡-5 mod10 →t ≡5 mod10. 输出5次。此时,青蛙A的位置是0+2*5=10 →0 mod10. 青蛙B的位置是5+3*5=20 →0 mod10. 确实相遇。 另一个案例: x=3,y=5,m=4,n=2,L=10 →a=4-2=2,b=5-3=2. 方程是2t ≡2 mod10 →等价于t≡1 mod5 →解是1,6,11,… 最小的正解是1. 所以,代码输出1. 综上,这个代码应该能够正确处理所有情况。</think>要解决这个问,我们需要判断两只青蛙是否会在某一时间点相遇。通过分析它们的运动规律,可以将问转化为求解线性同余方程的解。 ### 问分析 两只青蛙在同一条环形纬度线上跳跃,纬度线总长为$L$米。设青蛙A的起始位置为$x$,每次跳跃$m$米;青蛙B的起始位置为$y$,每次跳跃$n$米。我们需要找到最小的跳跃次数$t$,使得两只青蛙的位置相同。 ### 数学建模 两只青蛙在跳跃$t$次后的位置分别为$(x + m \cdot t) \mod L$和$(y + n \cdot t) \mod L$。相遇的条件可以表示为: $$(x + m \cdot t) \equiv (y + n \cdot t) \pmod{L}$$ 化简得: $$(m - n) \cdot t \equiv (y - x) \pmod{L}$$ 这是一个线性同余方程,形式为: $$a \cdot t \equiv b \pmod{L}$$ 其中,$a = m - n$,$b = y - x$。 ### 解决步骤 1. **处理符号问**:若$a < 0$,将方程两边取反,确保$a$为正。 2. **特判$a = 0$的情况**:若$a = 0$,则两只青蛙速度相同,只有当初始位置相同时才相遇。 3. **求解线性同余方程**:使用扩展欧几里得算法求最大公约数$d = \gcd(a, L)$。若$d$不整除$b$,则无解。 4. **求逆元和最小正解**:通过扩展欧几里得算法求出逆元,计算最小正整数解。 ### 代码实现 ```c #include <stdio.h> long long exgcd(long long a, long long b, long long *x, long long *y) { if (b == 0) { *x = 1; *y = 0; return a; } else { long long d = exgcd(b, a % b, y, x); *y -= a / b * (*x); return d; } } int main() { long long x, y, m, n, L; scanf("%lld%lld%lld%lld%lld", &x, &y, &m, &n, &L); long long a = m - n; long long b = y - x; // 处理a的符号 if (a < 0) { a = -a; b = -b; } if (a == 0) { // 青蛙速度相同 if (b % L == 0) { printf("0\n"); } else { printf("Impossible\n"); } return 0; } long long x0, y0; long long d = exgcd(a, L, &x0, &y0); if (b % d != 0) { printf("Impossible\n"); } else { long long L_prime = L / d; long long inv = (x0 % L_prime + L_prime) % L_prime; long long t0 = ((b / d) * inv) % L_prime; printf("%lld\n", t0); } return 0; } ``` ### 代码解释 1. **exgcd函数**:扩展欧几里得算法求解方程$ax + by = \gcd(a, b)$,并返回最大公约数。 2. **输入处理**:读取输入并处理$a$的符号,确保其为正。 3. **特判处理**:若$a = 0$,检查初始位置是否相同。 4. **求解同余方程**:使用扩展欧几里得算法求最大公约数$d$,若$d$不整除$b$则无解。 5. **计算逆元和最小解**:通过调整逆元得到最小的正整数解。 该算法高效且能够处理大整数范围,确保在合理时间内求解问
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值