学过OI,……我便考你一考。逆元的求法,怎样写的?
不能写罢?……我教给你,记着!这些算法应该记着。将来打国赛的时候,写代码要用。
“谁要你教,不是 么?”
XJ 显出极高兴的样子,将两个指头的长指甲敲着键盘,点头说,“对呀对呀!……求逆元有四种写法,你知道么?”
法1:费马小定理
对于指数 ,有 ,故有 。
代码:
ll mpow(ll x,ll y)
{
ll ans=1;
for(;y;y>>=1,x=x*x%p) if(y&1) ans=ans*x%p;
}
ll inv(ll x)
{
return mpow(x,p-2);
}
优点:代码简单,速度较快
缺点:仅适合 为质数的情况
法2:exgcd
令 ,则 ,即 ,由于 均为常数,所以可以直接利用 exgcd 算法求出 ,注意特判 和 不互质的情况即可。
代码:
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(!b) {x=1,y=0; return a;}
ll v=exgcd(b,a%b,x,y),t=x-a/b*y;
x=y,y=t; return v;
}
ll inv(ll x)
{
ll t1,t2;
if(exgcd(x,p,t1,t2)!=1) return -1;
return (t1%p+p)%p;
}
优点:适合所有情况
缺点:代码相比其他方法较繁琐,常数较大
法3:线性求逆元
定义 为 对 取模的值,则由取模的定义有:
两边同乘 得:
即:
代码:
ll f[N];
void init()
{
f[1]=1;
for(int i=2;i<=n;i++) f[i]=(p-p/i)*f[p%i]%p;
}
优点:需要求大量逆元的时候渐进复杂度优于前两种算法
缺点:常数较大;只能求从 开始的连续逆元所以只能求到最小质因数 ,因此一般只能用于 为质数的情况
法4:线性求任意逆元
令 为欲求逆元的数,,那么:
因此,预处理出 和数组 的前后缀积即可线性()求出 中所有数的逆元。
代码:
int n; ll a[N],b[N],f[N],g[N];
ll inv(ll x) {...}
void work()
{
f[0]=1; for(int i=1;i<=n;i++) f[i]=f[i-1]*a[i]%p;
g[n+1]=1; for(int i=n;i>=1;i--) g[i]=g[i+1]*a[i]%p;
ll tmp=inv(f[n]);
for(int i=1;i<=n;i++) b[i]=f[i-1]*g[i+1]%p*tmp%p;
}
优点:可以快速求出若干不相关数的逆元,不需要 为质数
缺点:
- 只要有一个数不与 互质就无法求出任何一个数的逆元
- 还需要一个求单个数逆元的算法,代码较繁琐
- 常数较大
XJ 刚打开了电脑,想在 VSC 里打字,见您毫不热心,便又叹一口气,显出极惋惜的样子。
(代码未提交,仅供示意,有错误请联系作者修改)
评论
发表评论