逆元入门
逆元(inv)
1.什么是逆元
当求解公式:(a/b)%m 时,因b可能会过大,会出现爆精度的情况,所以需变除法为乘法:
设c是b的逆元,则有b*c≡1(mod m);
则(a/b)%m = (a/b)*1%m = (a/b)*b*c%m = a*c(mod m);
即a/b的模等于a*b的逆元的模;
逆元就是这样应用的;
2.求逆元的方法
(1).费马小定理
在是素数的情况下,对任意整数都有。
如果无法被整除,则有。
可以在为素数的情况下求出一个数的逆元,,即为逆元。
题目中的数据范围1<=x<=10^9,p=1000000007,p是素数;
所以x肯定就无法被p整除啊,所以最后就得出x^(p-2)为x的逆元啦。
复杂度O(logn);
代码:
- const int mod = 1000000009;
- long long quickpow(long long a, long long b) {
- if (b < 0) return 0;
- long long ret = 1;
- a %= mod;
- while(b) {
- if (b & 1) ret = (ret * a) % mod;
- b >>= 1;
- a = (a * a) % mod;
- }
- return ret;
- }
- long long inv(long long a) {
- return quickpow(a, mod - 2);
- }
(2)扩展欧几里得算法求逆元
扩展欧几里得算法可以参考小白书;
百度百科-乘法逆元中有这样一个例子:
求x,k就是扩展欧几里得算法了吧~
可扩展欧几里得求逆元ax≡1(mod n)其中a,n互质;
复杂度:O(logn);
代码:
- ll extend_gcd(ll a, ll b, ll &x, ll &y) {
- if (b == 0) {
- x = 1, y = 0;
- return a;
- }
- else {
- ll r = extend_gcd(b, a % b, y, x);
- y -= x * (a / b);
- return r;
- }
- }
- ll inv(ll a, ll n) {
- ll x, y;
- extend_gcd(a, n, x, y);
- x = (x % n + n) % n;
- return x;
- }
(3) 逆元线性筛 ( P为质数 )
求1,2,...,N关于P的逆元(P为质数)
复杂度:O(N)
代码:
- const int mod = 1000000009;
- const int maxn = 10005;
- int inv[maxn];
- inv[1] = 1;
- for(int i = 2; i < 10000; i++)
- inv[i] = inv[mod % i] * (mod - mod / i) % mod;
如果是求阶乘的逆元呢?(阶乘数组:fac[ ])
代码:
- for(ll i=maxn-1;i>=0;i--)
- inv[i]=(inv[i+1]*(i+1))%mod;
还有一个定理可以用来求组合数:(卢卡斯定理)
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e6+5;
#define mod 1000000007
#define debug puts("ok!");
ll fac[maxn];
void init(){
fac[0] = fac[1] = 1;
for(int i = 2;i<maxn;i++) fac[i] = fac[i-1]*i%mod;
}
ll quick_mod(ll a,ll b){
ll res = 1;
while(b)
{
if(b&1) res = (res*a)%mod;
b >>= 1;
a = (a*a)%mod;
}
return res;
}
ll C(ll n,ll m){
if(m>n){
return 0;
} else {
return (fac[n]*(quick_mod(fac[n-m]*fac[m]%mod,mod - 2))%mod);
}
}
ll lucas(ll n, ll m){
if(m == 0){
return 1;
} else {
return C(n%mod,m%mod)*(lucas(n/mod,m/mod))%mod;
}
}
int main()
{
ll n,m;
init();
while(~scanf("%lld %lld",&n,&m))
{
printf("%lld\n",lucas(n,m-2));
}
return 0;
}
参考blog:
https://blog.csdn.net/raalghul/article/details/51752369
https://blog.csdn.net/wyg1997/article/details/52152282
http://www.voidcn.com/blog/qq_28954601/article/p-6227778.html、
https://menyf.gitbooks.io/acm-icpc-template/6_%E6%95%B0%E8%AE%BA/%E9%80%86%E5%85%83.html。