【好题选讲】P1516 青蛙的约会

才发现没从洛谷迁移过来。

前置知识:

扩展欧几里得算法 exgcd
这个题目集合了扩展欧几里得算法的不少坑点。

思路

观察题目,发现题目即为寻找一个正整数 \(t\),使得:

\[x+tm\equiv y+tn\ (\operatorname{mod} L ) \]

如果不存在,输出 Impossible
我们首先尝试将式子化简:

\[\begin{aligned} x+tm &\equiv y+tn\ (\operatorname{mod} L )\\ x+tm -y-tn&\equiv 0\ (\operatorname{mod} L )\\ t\left(m-n\right)&\equiv y-x\ (\operatorname{mod} L )\\ \end{aligned}\]

之后怎么办呢,我们要引入一个对答案没什么用的参数 \(b\),把原式变成这个样子:

\[t\left(m-n\right) = y-x+bL \ \ \ \ (b \in \mathbb{Z}) \]

很好理解:因为等号两边同余,因此左边一定等于右边的加上整数倍的 \(L\),可以是负数倍。
接着往下化简:

\[t\left(m-n\right)+\left(-bL\right) = y-x \]

我们发现,式子中的 \(m-n,L,y-x\) 均已知,于是这个式子成为了一个不定方程,我们要做的就是求解出这个方程中 \(t\)最小正整数解
不定方程,我们自然想到扩展欧几里得算法:

\[t\left(m-n\right)+\left(-bL\right) = \operatorname{gcd}(m-n,L) \]

但是,由于原不定方程的右边并不是 \(\operatorname{gcd}(m-n,L)\) ,因此这个方程有解的充要条件是 \(y-x\) 应是 \(\operatorname{gcd}(m-n,L)\) 的整数倍
像这样将不定方程化成扩展欧几里得算法能求解的方式也是其解题的一般套路。
于是我们可以快乐的套入扩展欧几里得,求解出 \(t\)

2. 坑点

但是,直接输出 \(t\) 就可以了吗?
要知道,\(t\) 值是满足这个不定方程的一组解:

\[t\left(m-n\right)+\left(-bL\right) = \operatorname{gcd}(m-n,L) \]

而非我们要求解的这个方程:

\[t\left(m-n\right)+\left(-bL\right) = y-x \]

因此,我们利用等式的基本性质,在 \(t\left(m-n\right)+\left(-bL\right) = \operatorname{gcd}(m-n,L)\) 同时乘上 \(\frac{y-x}{\operatorname{gcd}(m-n,L)}\),即可求解出满足那个方程的 \(t\)
但是,这样的 \(t\) 就正确了吗?我们发现 \(t\) 的值还可能是负数,所以还要用递推公式将 \(t\) 化为最小正整数。
但但是,这样的 \(t\) 就是正确的吗?我们发现 \(t\) 的值还可能不是最小正整数,因此还可能要对 \(\frac{L}{\operatorname{gcd}(m-n,L)}\) 取模以变成最小正整数解。

3. 代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll x,y,m,n,l;
ll exgcd(ll a,ll b,ll &c1,ll &c2){
	if(b==0){
		c1=1;
		c2=0;
		return a;
	}
	ll d=exgcd(b,a%b,c2,c1);
	c1=c1;
	c2=c2-a/b*c1;
	return d;
}
ll c1,c2;
int main(){
	
	cin>>x>>y>>m>>n>>l;
	
	ll cc=exgcd(m-n,l,c1,c2);
	if((y-x)%cc!=0){//不是倍数,输出无解 
		cout<<"Impossible";
		return 0;
	}
	c1*=((y-x)/cc);//先变成原不定方程的解 
	ll t=abs(l/cc);
	while(c1<0){//如果是负数,不断加直到正数  
		c1+=t;
	}
	cout<<c1%t;//如果不是最小正整数解,通过取余即可 
	
	return 0;
} 
posted @ 2025-07-28 09:15  hm2ns  阅读(23)  评论(0)    收藏  举报