1 条题解
-
0
自动搬运
来自洛谷,原作者为

学委
希望以后某时候还能来写题!搬运于
2025-08-24 21:20:43,当前版本为作者最后更新于2018-08-06 21:01:29,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
2020-02-27 更新
一步一步来。
问题转化
题目问的是满足 的最小正整数 。(是正整数)
但是不能暴力枚举 ,会超时。
把问题转化一下。观察 ,它的实质是 :这里 是我们新引入的某个整数,并且似乎是个负数才对。这样表示是为了用扩展欧几里得算法。我们将要努力求出一组 来满足这个等式。稍微再等一下——
问题还需要转化。扩展欧几里得是用来求 中的未知数的,怎么牵扯到等于 呢?
原理是,方程 有解的必要条件是 。
这个简单证一下。
由最大公因数的定义,可知 是 的倍数,且 是 的倍数,
若 都是整数,就确定了 是 的倍数,
因为 ,所以 必须是 的倍数,
那么 。
可得出在这道题中,方程 的有解的必要条件是 。可怜的 只能等于 了。这实际上就是 互质。
扩展欧几里得
前提:知道普通欧几里得算法(辗转相除法)。
下面字母挺多,希望你耐心地慢慢地读~
我们拿到了一组 。设 。那么目标是求出满足 (①) 的整数 与 。其中 应当是满足条件的最小正整数,即答案,而 是辅助答案。
(注意,虽然刚刚已经证明本题的 必然等于 ,但是扩展欧几里得算法本身过程求的是 的解。它正好非常适合本题,不过我们要按照它求解的过程去做)
如果我们先前已经求出了另一组数 ,它们满足这么一个式子:(②),则此时结合①②一定有:
(③)
可见,在这个“如果”实现的时候,我们的目标变成了“求出满足上式的 和 ”。
其中 都已知, 待求。因为未知数比方程更多,所以没有唯一解。我们先求出一组必然存在的解,最后将在“答案处理”时转为最小解。
怎么求呢?取模运算是 ,所以方程③实际上是:
看上面这个方程,一组必然存在的解出现了:
(④)
可见,我们只要求出 ,就能得出正确的 。问题是 怎么求。
现在我们手上是 这两个系数,而目标是求出 和 满足:
(②)
把①和②对比一下:
(①)
原方程中的系数 变成了②中的系数 ,原方程中的系数 变成了②中的 而已。
所以,把新的方程也看作 的形式(只是系数 和 的具体数值改变了)。然后按照上面的一模一样下来(其实都只是推导过程),我们发现,最好有 来支撑 。
再一模一样下来,我们又需要 来支撑 。
……
这个递归中 不断被替代为 ,这个替换方式与普通欧几里得是一样的,所以最后会出现 。
这时要直接返回了,我们需要一组 满足 (⑤)。
然而该层的 。所以只要⑤左边取 ,这个方程就妥妥的成立了。
(最后一层的 建议取 。然而由于 ,就算返回其它数值,方程也一定成立。但这样的程序容易出错,因为 在回溯时滚雪球式增长,容易数值越界。下面的代码在最后一层令 ,开了long long,通过了此题。)
最后一层结束后,就开始返回,直到最上层。每一层可以轻松地根据下层的 求出当前层的 。
整个过程就是:以辗转相除的方式向下递进,不断缩小系数,保证会出现有确定解的最后一层。
答案处理
一个遗留问题:我们将要求出来的 仅仅保证满足 ,而 不一定是最小正整数解。有可能太大,也有可能是负数。这依然可以解决,那就是, 批量地减去或加上 ,能保证 ,因为:
1.显然这并不会把方程中任何整数变成非整数。
2.“加上或减去 ”不会使 错过任何解。可以这么理解:
已经求出一组整数 使得 ,也就是 。 是整数,可见目前 是 的倍数。
现在想改变 并使得方程仍然成立。已知 互质,假若 的变化量()不是 的倍数,则 的变化量()也不是 的倍数,这会使得 不再是 的倍数,则 不是整数了。
仅当 的变化量是 的倍数时, 能保持自己是 的倍数,此时就出现新的解了。
因此到最后,如果 太小就不断加 直到大于等于 ,太大则一直减 ,直到最小正整数解。也就是这么写:
x = (x % b + b) % b;//括号中取模再加,可以处理负数代码
推导中的所有 共用全局变量 long long x, y,传递也很方便。
#include<bits/stdc++.h> using namespace std; long long x, y;//目前方程真正的解 void exgcd(long long a, long long b) { //当前目的:求解 ax + by = gcd(a, b) 这么一个方程 if(b == 0) //a, b不断改变的过程中,b最终必然会成为0 { //在 b = 0 时方程还要成立? 使 x = 1, y = 0 ,必然成立 x = 1; y = 7; //建议返回0。不过y = 7能AC,证明了最后一个等式不受最后一个y影响 return; } exgcd(b, a % b);//把下一层系数传进去(先求下一个方程的解 ) //现在我们已经拿到了下一个方程的解x, y long long tx = x;//暂时存一下x,别丢了 x = y; y = tx - a / b * y; } int main() { long long a, b; cin >> a >> b; exgcd(a, b); x = (x % b + b) % b;//我们求出来的x必然满足方程,但不一定是最小正整数解,所以要进行答案处理 printf("%lld\n", x); return 0; }求这样一个满足 的 有什么用呢?一个重要用途如下。
这个 就是: 在模 意义下的乘法逆元。
在模 意义下,如果想要除以 就非常麻烦。这时候乘以 的逆元等效于除以 。
假设我们已经算出了:
ans = (a * b * c) % p;现在要从 中把 给除掉,如何处理 ?
如果像下面这样直接除会出错(举个实例会很直观):
ans = ans / b % p;所以我们就求出 的逆元 (满足 ),然后直接算这个:
ans = ans * x % p;原理可以这么理解:
更新记录:
2020-02-01 证明略有改动;加入了逆元一种作用的说明。
2020-02-03 加点补充说明减少误解。
2020-02-27 语句修改。
- 1
信息
- ID
- 84
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 4
- 标签
- 递交数
- 1
- 已通过
- 0
- 上传者