题目传送门
题目大意:给你一个正整数n,一个模数p,要你求从1~n中每一个数的逆元。
数据范围:1≤n≤3×106 ,n<p<20000528;保证p是质数。
方法1:费马小定理
对于p是质数的情况下,我们可以用快速幂进行求逆元,原理是费马小定理。
费马小定理:对于质数p,ap-1 ≡ 1(mod p),我们要求a的在模p意义下的逆元的时候,有a*inv(a)≡1(mod p),又根据根据费马小定理, a * ap-2≡1(mod p),所以我们有inv(a)≡ap-2,所以我们可以用快速幂很快的求出a在模p意义下的逆元。
在你用方法1写完链接的这道题之后,会发现TLE了,因为这个时间复杂度为nlogp,在500ms的要求下是跑不完的,所以我们要另辟蹊径。
方法2:线性算法(用于求一连串数字的逆元)
首先,1的逆元是1。
假设我们有一个数字p,令p=k * i+r,即k是 x/i 的商,r是余数。
则 :k * i+r≡0(mod p),
两边同时乘:inv( i ) * inv( r )
则得到:k * inv ( r )+inv(i)≡0
即:inv(i)=-k * inv( r )
即:inv(i)=-(p/i) * inv(p%i).
继续转化:inv(i)=(p-p/i) * inv(p%i)
而p%i肯定要比i小,之前求过,所以可以直接拿来用。
写成代码就是
inv[1] = 1;
for(int i = 2; i < p; ++ i)
inv[i] =(ll) (p - p / i) * inv[p % i] % p;
记得开long long,因为有时候逆元比较大,乘法容易爆精度。
方法3:拓展欧几里得(exgcd)
exgcd是求不定方程ax+by=c的解,由逆元定义,a * inv(a)≡1(mod p),即a * inv(a)+p * y=1,由于a,p已知,这个式子就可以用exgcd求解。
代码
void exgcd(int a,int b,int &x,int &y)
{
if(!b)
{x=1;y=0;return ;}
exgcd(b,a%b,x,y);
int t=y;
y=x-a/b*y;
x=t;
}
signed main()
{
int n,p;
cin>>n>>p;
int x,y;
for(int i=1;i<=n;i++)
{
exgcd(i,p,x,y);
if(x>0&&x%p!=0)
x=x%p;
else
x=x%p+p;
cout<<x<<endl;
}
}