整理自:https://www.cnblogs.com/linyujun/category/784324.html
首先,引入同余定理:
(a + b) % p = (a%p + b%p) %p (对)
(a - b) % p = (a%p - b%p) %p (对)
(a * b) % p = (a%p * b%p) %p (对)
(a / b) % p = (a%p / b%p) %p (错)
为什么除法是错的呢,举一个例子就知道了:(100/50)%20 = 2 ≠ (100%20) / (50%20) %20 = 0
但是在有些题目中我们又不得不在除法的时候求余,这时候就要引入一个新的概念了,数论倒数,又称逆元。
我们知道,如果,a*x = 1,那么x是a的倒数,x = 1/a,那数论中,大部分情况都有求余,所以现在问题变了
a*x = 1 (mod p),那么x一定等于1/a吗,不一定,
所以这时候,我们就把x看成a的倒数,只不过加了一个求余条件,所以x叫做 a关于p的逆元
比如2 * 3 % 5 = 1,那么3就是2关于5的逆元,或者说2和3关于5互为逆元
这里3的效果是不是跟1/2的效果一样,所以才叫数论倒数
a的逆元,我们用inv(a)来表示(a和p互质,a才有关于p的逆元)
a^(p-1) ≡1 (mod p)
两边同除以a
a^(p-2) ≡1/a (mod p)
什么,这可是数论,还敢写1/a
应该写a^(p-2) ≡ inv(a) (mod p)
所以inv(a) = a^(p-2) (mod p)
这个用快速幂求一下,复杂度O(logn)
费马小定理中p要求是质数,如果p不是素数,就不能用费马小定理求了
求出来的逆元不一定是最小的
#include
typedef long long LL;
LL pow_mod(LL a, LL b, LL p) //a的b次方求余p
{
LL ret = 1;
while(b)
{
if(b & 1) ret = (ret * a) % p;
a = (a * a) % p;
b >>= 1;
}
return ret;
}
LL Fermat(LL a, LL p) //费马求a关于b的逆元
{
return pow_mod(a, p-2, p);
}
int main()
{
LL a, p;
scanf("%lld%lld", &a, &p);
printf("%lld\n", Fermat(a, p));
}
a*x + b*y = 1
如果ab互质,有解
这个解的x就是a关于b的逆元
y就是b关于a的逆元
为什么呢?
你看,两边同时求余b
a*x % b + b*y % b = 1 % b
a*x % b = 1 % b
a*x = 1 (mod b)
你看你看,出现了!!!(/≥▽≤/)
所以x是a关于b的逆元
反之可证明y
#include
typedef long long LL;
void ex_gcd(LL a, LL b, LL &x, LL &y, LL &d)
{
if (!b)
{
d = a, x = 1, y = 0;
}
else
{
ex_gcd(b, a % b, y, x, d);
y -= x * (a / b);
}
}
LL inv(LL t, LL p) //如果不存在,返回-1
{
LL d, x, y;
ex_gcd(t, p, x, y, d);
return d == 1 ? (x % p + p) % p : -1;
}
int main()
{
LL a, p;
while(~scanf("%lld%lld", &a, &p))
{
printf("%lld\n", inv(a, p));
}
}
能求解什么问题呢?
问题:
一堆物品
3个3个分剩2个
5个5个分剩3个
7个7个分剩2个
问这个物品最少有多少个?
模板:
#include
#include
using namespace std;
//扩展欧几里得算法
int exgcd(int a,int b,int &x,int &y)
{
int d;
if(b==0)
{
x=1;y=0;
return a;
}
d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
//中国剩余定理 ,r[]存放余数 ,prime[]存放两两互质的数
int Chinese_Remainder(int r[],int prime[],int len)
{
int i,d,x,y,m,n=1,sum=0;
//计算所以除数的积n,也是所以除数的最小公倍数
for(i=0;i
比如poj 2891 http://poj.org/problem?id=2891
【题目大意】
给出k个模方程组:x mod ai = ri。求x的最小正值。如果不存在这样的x,那么输出-1.
【题目分析】
由于这道题目里面的ai、ri之间不满足两两互质的性质,所以不能用中国剩余定理直接求解。
AC代码如下:
#include
#include
using namespace std;
typedef long long LL;
typedef pair PLL;
LL a[100000], b[100000], m[100000];
LL gcd(LL a, LL b){
return b ? gcd(b, a%b) : a;
}
void ex_gcd(LL a, LL b, LL &x, LL &y, LL &d){
if (!b) {d = a, x = 1, y = 0;}
else{
ex_gcd(b, a % b, y, x, d);
y -= x * (a / b);
}
}
LL inv(LL t, LL p){//如果不存在,返回-1
LL d, x, y;
ex_gcd(t, p, x, y, d);
return d == 1 ? (x % p + p) % p : -1;
}
PLL linear(LL A[], LL B[], LL M[], int n) {//求解A[i]x = B[i] (mod M[i]),总共n个线性方程组
LL x = 0, m = 1;
for(int i = 0; i < n; i ++) {
LL a = A[i] * m, b = B[i] - A[i]*x, d = gcd(M[i], a);
if(b % d != 0) return PLL(0, -1);//答案,不存在,返回-1
LL t = b/d * inv(a/d, M[i]/d)%(M[i]/d);
x = x + m*t;
m *= M[i]/d;
}
x = (x % m + m ) % m;
return PLL(x, m);//返回的x就是答案,m是最后的lcm值
}
int main(){
int n;
while(scanf("%d", &n) != EOF){
for(int i = 0; i < n; i ++){
a[i] = 1;
scanf("%d%d", &m[i], &b[i]);
}
PLL ans = linear(a, b, m, n);
if(ans.second == -1) printf("-1\n");
else printf("%I64d\n", ans.first);
}
}
下面是两个求乘法逆元的模板 转载自:https://blog.csdn.net/dreamdraw_pan/article/details/52959669
辗转相除法求逆元(求a对于mod的逆元, 要求a与mod互素)
ll exgcd(ll a, ll b, ll &x, ll &y)
{
if (b == 0)
{
x = 1;
y = 0;
return a;
}
ll r = exgcd(b, a % b, x, y);
ll t = x % mod;
x = y % mod;
y = ((t - a / b * y) % mod + mod) % mod;
return r;
}
求2对于1e9+7的逆元就是 exgcd(2, 1e9+7, x, y),其中x的值就是inv2,
费马小定理求逆元(求a对于mod的逆元,要求mod为素数)
ll power_mod(ll a, ll b, ll mod)
{
ll ans = 1;
while (b)
{
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
inv2 = power_mod(a, mod - 2, mod);
首先, ax+by = gcd(a, b) 这个公式肯定有解
所以 ax+by = gcd(a, b) * k 也肯定有解
所以,这个公式我们写作ax+by = d,(gcd(a, b) | d)
gcd(a, b) | d,表示d能整除gcd,这个符号在数学上经常见
那么已知 a,b 求 一组解 x,y 满足 ax+by = gcd(a, b) 这个公式
#include
typedef long long LL;
void ex_gcd(LL a, LL b, LL &x, LL &y, LL &d)
{
if (!b)
{
d = a, x = 1, y = 0;
}
else
{
ex_gcd(b, a % b, y, x, d);
y -= x * (a / b);
}
}
int main()
{
LL a, b, d, x, y;
while(~scanf("%lld%lld", &a, &b))
{
ex_gcd(a, b, x, y, d);
printf("%lld*a + %lld*b = %lld\n", x, y, d);
}
}