拓展欧几里得求逆元
求从1-n中mod p的逆元
inline ll exgcd(ll a,ll b,ll &x,ll &y){
return !b?(x=1,y=0,a):(exgcd(b,a%b,y,x),y-= (a/b)*x);
}
int main(){
ll n,p;
cin>>n>>p;
for(ll i=1;i<=n;i++){
ll j,k;
//ij≡1(mod p) ij+p(-k)=1
ll d=exgcd(i,p,j,k);
k=-k;
j=(j%p+p)%p;
cout<<j<<endl;
}
}
快速幂求逆元
ij≡1(mod p)由费马小定理知ij≡i^(p-1)(mod p),即j≡i^(p-2)(mod p)
然后就开始快乐的快速幂把
!!!!!快速幂只限于p是素数的情况!!!!!
ll qpow(ll x,ll n,ll p){//x^n mod p
x%=p;
ll ans=1;
while(n){
if(n&1)ans=qmul(x,ans,p);
n/=2;
x=qmul(x,x,p);
}
return ans;
}
int main(){
ll n,p;
cin>>n>>p;
for(ll i=1;i<=n;i++){
ll j;
j=qpow(i,p-2,p);
cout<<j<<endl;
}
}
上述两种方法求线性逆元都较慢,下给出线性逆元求法
这里记对于每个i的乘法逆元为(事实上也就是这么写的),所以我们只需要求
.
首先我们有一个,=1 ( modp ),然后设 p=k*i+r,(1<r<i<p)
则k*i+r≡0(mod p)
然后乘上,
就可以得到:
k*+
≡0(mod p)
≡−k∗
(modp),即
≡−⌊p/i⌋∗
(modp)
于是,我们就可以从前面推出当前的逆元了。
1.递推公式为inv[i]=-(p/i)*inv[p%i]
(这里注意到前面一项为负数,进行一下体调整)
2.inv[i]=(p-p/i)*inv[p%i]
(这里加上了一个p*inv[p%i],但由于是mod p不会影响)
3.inv[i]=(p-p/i)*inv[p%i]%p //最终递推公式
根据题目的i的规模,可以采用记忆化递推进行优化
ll p,n;//multiplicative inverse
cin>>n>>p;
inv[1]=1;inv[0]=0;
for(ll i=2;i<=n;i++){
inv[i]=inv[p%i]*(p-p/i)%p;
}
ps:当 inv[i]=0时,表示不存在乘法逆元
下面是一些例题和题解
P1082 [NOIP2012 提高组] 同余方程
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define LOCAL
inline ll exgcd(ll a,ll b,ll &x,ll &y){
return !b?(x=1,y=0,a):(exgcd(b,a%b,y,x),y-= (a/b)*x);
}
int main(){
ll a,b,x,k;
cin>>a>>b;
//ax≡1(mod b) ax+bk=1
//ax≡a^b-1(mod b) x≡a^b-2(mod b)
ll d=exgcd(a,b,x,k);
x=(x%b+b)%b;
cout<<x;
}
P3811 【模板】乘法逆元(线性求逆元)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll inv[3000010];
int main(){
ll p,n;
scanf("%lld%lld",&n,&p);
inv[1]=1;inv[0]=0;
for(ll i=2;i<=n;i++) inv[i]=inv[p%i]*(p-p/i)%p;
for(ll i=1;i<=n;i++) printf("%lld\n",inv[i]);
}
第一次用cin,cout被卡了 。。。。。。
P2054 [AHOI2005]洗牌
根据题目意思有下图(李姐一下,蹩脚的画图技术)
1.首先令n=2k,原位置L,洗牌后的位置L';
2.1当L>k时 L'=(L-k)*2-1=2L-n-1
2.2当1=<L<=k时 L'=2L
3.则L'≡2L(mod n+1)
1.设初始位置为P,末位置为L(已知)也就是P*
≡L(mod n+1)
2.P≡L*
(mod n+1),下面只用求
的乘法逆元即可
3.记
≡r(mod n+1)(这里使用快速幂求r)
4.然后就是最简单的乘法逆元了
又是被文件重复定向坑的一天
用longlong洛谷只有84,最后一组数据会爆longlong
这里用了__int128,注意输入输出要用快读快写
上述ll代表__int128,因为之前是用longlong 错了,懒得改了
P4071 [SDOI2016]排列计数
题目:求有多少种 1 到 n的排列 a,满足序列恰好有 m 个位置,使得
=i。答案对 10^9 + 7取模。
注意1e9+7是十位数中第一个素数(所以可以快速幂了)
考虑恰有m个位置
!=i,那么对于j+1个位置不同分3类,这里从n个数中选了m个位置不同,则共有
种取法,记m个位置均不满足
=i的取法有
种
1.前j个元素均满足题设,将j+1与前j个数中的任意一个交换得到j+1个位置不同的序列,也就是j*
种
2. 前j个元素中仅j-1个满足题设,j*
种
3. 前j个元素中小于j-1个满足题设,那么新引入的j+1,与前面不满足题设的位置交换,肯定至少还剩1个位置不满足,也就是这样的排列是不行的
综上
,其中
,
超时代码!!!!!!洛谷60分
#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline ll read(){
char ch=getchar();ll s=0,w=1;
while(ch<48||ch>57)
if(ch=='-')w=-1;ch=getchar();
while(ch>=48&&ch<=57)
s=(s<<1)+(s<<3)+ch-48;ch=getchar();
return s*w;
}
inline ll qmul(ll a,ll b,ll mod){//a*b mod mod
ll ans=0;
while(b){
if (b&1)ans=(ans+a)%mod;
b>>=1;
a=a<<1%mod;
}
return ans;
}
inline void write(ll x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
const ll p=1e9+7;
ll inv[1000005],A[1000005],step[1000005];
int main(){
ll T;
T=read();
inv[1]=1,inv[0]=1,A[0]=1,A[2]=1,step[1]=1;
//A[i]=(i-1)(A[i-1]+A[i-2]) i>=3;
for(ll i=3;i<=1e6+5;i++)
A[i]=qmul(i-1,A[i-1]+A[i-2],p);
//求组合数的模
//注意到组合数中有除法,由于除法的模时候没有意义的,所以这时候就要求逆元
//所以只需求1到m的乘法逆元,由于是线性的,用线性求逆元的方法
for(ll i=2;i<=1e6+5;i++)
inv[i]=inv[p%i]*(p-p/i)%p;
//这里求了inv[i]!的模
for(ll i=2;i<=1e6+5;i++)
inv[i]=inv[i]*inv[i-1]%p;
//i!的模
for(ll i=2;i<=1e6+5;i++)
step[i]=step[i-1]*i%p;
while(T--){
ll n,m,c_mod=1;
n=read(),m=read();
c_mod=qmul(inv[n-m],inv[m],p);
c_mod=qmul(c_mod,step[n],p);
write(qmul(c_mod,A[n-m],p));putchar('\n');
}
}
然后我把qmul删了 ,因为这题好像不会爆long long ,然后就快乐ac了哈哈哈
#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline ll read(){
char ch=getchar();ll s=0,w=1;
while(ch<48||ch>57)
if(ch=='-')w=-1;ch=getchar();
while(ch>=48&&ch<=57)
s=(s<<1)+(s<<3)+ch-48;ch=getchar();
return s*w;
}
inline void write(ll x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
const ll p=1e9+7;
ll inv[1000005],A[1000005],step[1000005];
int main(){
ll T;
T=read();
inv[1]=1,inv[0]=1,A[0]=1,A[2]=1,step[1]=1;
//A[i]=(i-1)(A[i-1]+A[i-2]) i>=3;
for(ll i=3;i<=1e6+5;i++)
A[i]=(i-1)*(A[i-1]+A[i-2])%p;
//求组合数的模
//注意到组合数中有除法,由于除法的模时候没有意义的,所以这时候就要求逆元
//所以只需求1到m的乘法逆元,由于是线性的,用线性求逆元的方法
for(ll i=2;i<=1e6+5;i++)
inv[i]=inv[p%i]*(p-p/i)%p;
//这里求了inv[i]!的模
for(ll i=2;i<=1e6+5;i++)
inv[i]=inv[i]*inv[i-1]%p;
//i!的模
for(ll i=2;i<=1e6+5;i++)
step[i]=step[i-1]*i%p;
while(T--){
ll n,m,c_mod=1;
n=read(),m=read();
c_mod=inv[n-m]*inv[m]%p;
c_mod=c_mod*step[n]%p;
write(c_mod*A[n-m]%p);putchar('\n');
}
}
P4942 小凯的数字
首先一个数mod 9的余数等于它各个位上的和mod 9的余数,也就是这题求(l+r)(l-r+1)/2 mod 9(等差数列求和)注意这里由有2,运用逆元,可以将其变为乘5.
#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline ll read(){
char ch=getchar();ll s=0,w=1;
while(ch<48||ch>57){
if(ch=='-')w=-1;ch=getchar();
}
while(ch>=48&&ch<=57){
s=(s<<1)+(s<<3)+ch-48;ch=getchar();
}
return s*w;
}
inline void write(ll x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
int main(){
ll t;
t=read();
while(t--){
ll l,r,ans;
l=read(),r=read();
if(l>r)swap(l,r);
ans=(l+r)%9*(r-l+1)%9*5%9;
write(ans);putchar('\n');
}
}
P2613 【模板】有理数取余
由于19260817是质数,所以不会angry
哈哈哈哈哈哈然后求乘法逆元直接上小费马就可以了
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll ans,a,b,p=19260817;
string s1,s2;
ll qpow(ll x,ll n){
ll res=1;
while(n){
if(n&1)res=res*x%p;
n>>=1;
x=x*x%p;
}
return res;
}
int main(){
cin>>s1>>s2;
for(int i=0;i<s1.size();i++){
a=a*10+s1[i]-'0';
a%=p;
}
for(int i=0;i<s2.size();i++){
b=b*10+s2[i]-'0';
b%=p;
}
cout<<a*qpow(b,p-2)%p;
return 0;
}
P5431 【模板】乘法逆元 2 题解
这道题是真的毒瘤,卡常凡死了;
首先将所求通分
,这样求就只用求一次逆元了
然后预处理a[i]的前缀积后缀积,O(1)时间求出分子中的连乘
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5*1e6;
ll n,k1=1,p,k,ans,s1[N+10],s2[N+10],a[N+10];
inline ll qpow(ll x,ll n){
ll res=1;
while(n){
if(n&1)res=res*x%p;
n>>=1;
x=x*x%p;
}
return res;
}
inline int read(){
char ch=getchar();int s=0,w=1;
while(ch<48||ch>57)
if(ch=='-')w=-1;ch=getchar();
while(ch>=48&&ch<=57)
s=(s<<1)+(s<<3)+ch-48;ch=getchar();
return s*w;
}
void inti(){
n=read(); p=read(); k=read();
s1[0]=s2[n+1]=1;
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=n;i++) s1[i]=s1[i-1]*a[i]%p;
for(int i=n;i>=1;i--) s2[i]=s2[i+1]*a[i]%p;
}
int main(){
inti();
ll inv=qpow(s1[n],p-2);
for(int i=1;i<=n;i++){
k1=k1*k%p;
ans=ans+s1[i-1]*s2[i+1]%p*k1%p;
ans%=p;
}
cout<<ans*inv%p;
return 0;
}