求解逆元

逆元:对于正整数a,如果有ax≡1 (mod m),那么把这个同余方程中的最小正整数解x叫做a模m的逆元。

对于缩系中的元素,每个数a均有唯一的与之对应的乘法逆元x,使得ax≡1(mod n)
一个数有逆元的充分必要条件是gcd(a,n)=1,此时逆元唯一存在 
逆元的含义:模n意义下,1个数a如果有逆元x,那么除以a相当于乘以x。

这里介绍三种方法求逆元:

  1、费马小定理求逆元。

  2、拓展gcd求逆元。

  3、欧拉定理。

 费马小定理求逆元

 在模为素数p的情况下,有费马小定理 a^(p-1)=1(mod p) 那么a^(p-2)=a^-1(mod p) 也就是说a的逆元为a^(p-  2)而在模不为素数p的情况下,有欧拉定理 a^phi(m)=1(mod m) (a⊥m) 同理a^-1=a^(phi(m)-1)因此逆元x便可  以套用快速幂求得了x=a^(phi(m)-1)但是似乎还有个问题?如何判断a是否有逆元呢? 检验逆元的性质,看求出值  x与a相乘是否为1即可

 PS:这种算法复杂度为O(log2N)在几次测试中,常数似乎较上种方法大

 当p比较大的时候需要用快速幂求解

 代码如下:

typedef  long long ll;  
ll pow_mod(ll x, ll n, ll mod){  
    ll res=1;  
    while(n>0){  
        if(n&1)res=res*x%mod;  
        x=x*x%mod;  
        n>>=1;  
    }  
    return res;  
}

拓展gcd求逆元

给定模数m,求a的逆相当于求解ax=1(mod m)这个方程可以转化为ax-my=1 然后套用求二元一次方程的方法,用拓展欧几里得算法求得一组x0,y0和gcd 检查gcd是否为1 ,gcd不为1则说明逆元不存在 ,若为1,则调整x0到0~m-1的范围中即可

PS:这种算法效率较高,常数较小,时间复杂度为O(ln n)

代码如下:

typedef  long long ll;  
void extgcd(ll a,ll b,ll& d,ll& x,ll& y){  
    if(!b){ d=a; x=1; y=0;}  
    else{ extgcd(b,a%b,d,y,x); y-=x*(a/b); }  
}  
ll inverse(ll a,ll n){  
    ll d,x,y;  
    extgcd(a,n,d,x,y);  
    return d==1?(x+n)%n:-1;  
} 

欧拉定理求逆元

当模p不是素数的时候需要用到欧拉定理a^phi(p)≡1(mod p),a*a^(phi(p)-1)≡1(mod p)
a^(-1)≡a^(phi(p)-1)(mod p)
所以 aϕ(m)1a
时间复杂度 O(n) 即求出单个欧拉函数的值
(当p为素数的时候phi(p)=p-1,则phi(p)-1=p-2可以看出欧拉定理是费马小定理的推广)
PS:这里就贴出欧拉定理的板子,很少会用欧拉定理求逆元

逆元的一般应用

  由于   ax≡1 (mod m)               ,

  故     x =1/a(mod m)

  故     (b/a)modm

  ===>(b*x)modm

  ===>(b%m* x%m)%m

我们来看几道例题:

一,ddu-1576

                    A/B

                                                         Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
                                                         Total Submission(s): 7160    Accepted Submission(s): 5700


Problem Description
要求(A/B)%9973,但由于A很大,我们只给出n(n=A%9973)(我们给定的A必能被B整除,且gcd(B,9973) = 1)。
 

Input
数据的第一行是一个T,表示有T组数据。
每组数据有两个数n(0 <= n < 9973)和B(1 <= B <= 10^9)。
 

Output
对应每组数据输出(A/B)%9973。
 

Sample Input
    
    
2 1000 53 87 123456789
 

Sample Output
    
    
7922 6060
分析:

ans= (A/B)%9973

   =(A*b)%9973            b B的逆元

   =(A%9973 * b%9973) %9973

   =n*b%9973

故只需求B的逆元即可得到答案。

代码实现:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define mod(x,y) (x%y+y)%y
#define ll long long int
using namespace std;
long long exgcd(ll a,ll b,ll &x,ll &y)
{
	if(b==0)
	{
		x=1;
		y=0;
		return a;
	}
	ll re=exgcd(b,a%b,y,x);
	y=y-x*(a/b);
	return re; 
}
long long ine(ll a,ll n)
{
	ll x,y;
	ll gcd=exgcd(a,n,x,y);
	return  mod(x,n);
}
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
	    ll n,b;
		cin>>n>>b;
		ll m=n*ine(b,9973)%9973;
		cout<<m<<endl; 
	} 
return 0;	
} 

二, hdu-5685

                 Problem A

                                                 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
                                                 Total Submission(s): 1635    Accepted Submission(s): 737


Problem Description
度熊手上有一本字典存储了大量的单词,有一次,他把所有单词组成了一个很长很长的字符串。现在麻烦来了,他忘记了原来的字符串都是什么,神奇的是他竟然记得原来那些字符串的哈希值。一个字符串的哈希值,由以下公式计算得到:

H(s)=ilen(s)i=1(Si28) (mod 9973)

Si 代表 S[i] 字符的 ASCII 码。

请帮助度熊计算大字符串中任意一段的哈希值是多少。
 

Input
多组测试数据,每组测试数据第一行是一个正整数 N ,代表询问的次数,第二行一个字符串,代表题目中的大字符串,接下来 N 行,每行包含两个正整数 a b ,代表询问的起始位置以及终止位置。

1N1,000

1len(string)100,000

1a,blen(string)
 

Output
对于每一个询问,输出一个整数值,代表大字符串从 a 位到 b 位的子串的哈希值。
 

Sample Input
    
    
2 ACMlove2015 1 11 8 10 1 testMessage 1 1
 

Sample Output
    
    
6891 9240 88

 代码实现:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
typedef long long ll; 
#define mod(x,y) (x%y+y)%y
#define N 100005 
using namespace std;
char str[N];
int sum[N];
void exgcd(ll a,ll b,ll &x,ll &y)
{
	if(b==0)
	{
		x=1;
		y=0;
		return ;
	}
	exgcd(b,a%b,y,x);
	y=y-x*(a/b);
}
long long ine(ll a,ll n)
{
	ll x,y;
	exgcd(a,n,x,y);
	return mod(x,n); 
}
int main()
{
	int t;
	while(cin>>t)
	{
		scanf("%s",str+1); 
	//	int len=strlen(str);
	    sum[0]=1;
		for(int i=1;str[i]!=0;i++)
		{
			sum[i]=(sum[i-1]*(str[i]-28))%9973;
		}
		for(int i=0;i<t;i++)
		{
	    ll a,b;
		cin>>a>>b;
		ll tmp=ine(sum[a-1],9973);
		ll m=sum[b]*tmp%9973;
		cout<<m<<endl;	
	    }
	} 
return 0;
 } 




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值