数论学习笔记
扩展欧几里得(exgcd)
模板先打上
void exgcd(ll a,ll b)
{
if(!b)
{
x1=1;y1=0;return;
}
exgcd(b,a%b);
ll t=x1;
x1=y1;y1=t-a/b*y1;
}
扩欧一般用来求解形如 \(ax+by=\gcd(a,b)\) 的不定方程,同时也可以用于求乘法逆元,这之后再讲。
一般来说,通过上述函数求出来的只是一组特解,很多情况下并不是我们需要的答案,所以还要对其进行处理。大多数情况下,题目都会要求我们求 \(x\) 的最小正整数解,此时 \(x\) 一般等于 \((x\%p+p)\%p\) ,这就是 \(x\) 的最小正整数解。
最简单的一道例题:P1082 同余方程
再来看更难的模板题:P5656 【模板】二元一次不定方程(exgcd)
这里要求的是 \(ax+by=c\) 的解,要求很多,我们一个一个来看。首先判无解的情况,这里我设 \(d=\gcd(a,b)\),可以发现,如果 \(d\nmid c\) ,那么这个方程就没有整数解。
为什么呢?可以证明,因为题中 \(x,y\) 均为整数,且 \(a\) 和 \(b\) 一定是 \(d\) 的倍数,所以 \(ax+by\) 也一定是 \(d\) 的倍数,又因为 \(c=ax+by\) ,所以 \(c\%d=0\) 可以得证。
因此,若 \(d\nmid c\) ,则方程无整数解。
若有整数解,我们可以设 \(a'=a/d,b'=b/d,c'=c/d\) , 并用 \(exgcd\) 求出 \(a'x_0+b'y_0=1\) 的一组整数解 \(x_0,y_0\) ,则 \(a'c'x_0+b'c'y_0=c'\) ,两边再同乘 \(d\) ,就得到 \(ac'x_0+bc'x_0=c\) ,由此得到原方程的一组特解 \(x_1=c'x_0,y_1=c'y_0\) 。
此时我们可以先把 \(x\) 的最小正整数解算出来,即 \(x_{min}=(x\%b+b)\%b\) ,要注意的是若 \(x\%b=0\) ,则 \(x_{min}=b\) 。
观察方程可知,当 \(x\) 越小时,\(y\) 越大,所以此时与 \(x_{min}\) 对应的那个 \(y\) 即为 \(y\) 的最大正整数解。因为 \(a'x_{min}+b'y_{max}=c'\) ,所以 \(y_{max}=\frac{c'-a'x_{min}}{b'}\) 。
若此时的 \(y_{max}>0\) ,则说明有正整数解,于是可以同理求出 \(x_{max}\) 与 \(y_{min}\) ,然后因为 \(x_{min}\) 与 \(x_{max}\) 之间每隔 \(b'\) 个数就有一个解,所以正整数解的个数为 \((x_{max}-x_{min})/b'+1\) 。
若此时 \(y_{max}\le 0\) ,则说明没有正整数解,同理计算出 \(y_{min}\) 即可。
习题
乘法逆元
定义:对于形如 \(ax\equiv 1\pmod b\) 的同余方程,此时 \(x\) 的最小正整数解即为 \(a\) 的逆元。
是不是感觉有些熟悉?没错,这和我们上面的例题P1082一模一样。可以将其化为 \(ax+by=1\) 的不定方程,并使用扩欧求解。
但求逆元不只能用扩欧,还可以用费马小定理+快速幂,但是博主不会,所以等以后再讲。
模板题:P3811 【模板】乘法逆元
除了上述两种方法,还有一种 \(O(n)\) 求逆元的递推方法。这里给出公式,先设 \(inv(i)\) 为 \(i\) 在模 \(p\) 意义下的逆元,则 \(inv(i)=(ll)p-p/i*inv(p\%i)\%p\) ,初始条件为 \(inv(1)=1,inv(0)=0\) 。
如果要求某一个数模 \(p\) 的逆元,可以使用此公式递归求解,证明请见模板题解。
中国剩余定理(CRT)
中国剩余定理是用于求解形如
同余方程组的定理,其中 \(m_1,m_2,\ldots,m_k\) 为两两互质的整数,要求找出 \(x\) 的最小非负整数解。
求解过程
首先设 \(M\) 为 \(\prod_{i=1}^km_i\) ,\(M_i=\frac{M}{m_i}\), \(M_it_i\equiv 1\pmod {m_i}\)。
可以看出来 \(t_i\) 即为 \(M_i\) 的逆元,可以构造出一个解 \(x_0=\sum_{i=1}^ka_iM_it_i\) ,我们要求的答案 \(x\) 即为 \(x_0\%M\) 。
证明请看题解,搞不搞懂无所谓谁叫信息学只看答案呢
模板题:P1495 【模板】中国剩余定理(CRT)/曹冲养猪
主要部分代码
void exgcd(ll a,ll b,int i)
{
if(!b)
{
x[i]=1;y[i]=0;return;
}
exgcd(b,a%b,i);
ll t=x[i];
x[i]=y[i];y[i]=t-a/b*y[i];
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld%lld",&a[i],&b[i]);
M*=a[i];
}
for(int i=1;i<=n;i++)
m[i]=M/a[i];
for(int i=1;i<=n;i++)
{
exgcd(m[i],a[i],i);
X+=b[i]*m[i]*(x[i]<0?x[i]+a[i]:x[i]);
}
printf("%lld",X%M);
return 0;
}