矩阵快速幂 逆元

二进制快速幂 O(logn)

typedef long long ll;
ll mod;
ll qpow(ll a, ll n)//计算a^n % mod
{
    ll re = 1;
    while(n)
    {
        if(n & 1)//判断n的最后一位是否为1
            re = (re * a) % mod;
        n >>= 1;//舍去n的最后一位
        a = (a * a) % mod;//将a平方
    }
    return re % mod;
}

十进制快速幂

例题:https://www.luogu.org/problem/P1226

由于这道题的指数还在longlong long longlonglong的范围内,比较小,快速幂可以用二进制的来实现。

但是我们考虑到在指数非常大时该怎么办,例如k kk=10 1010100000。如果你想写高精那么我不会阻挡你,只要你不嫌麻烦。

那么现在进入正题,如果你了解了快速幂的本质,那么十进制快速幂不是什么问题,如果不了解,请先去仔细阅读别的题解。

十进制快速幂和二进制没有什么本质的区别,一个是十进制拆分指数,另一个是二进制拆分指数。

例如3 33405可以拆分为 (3 331)4 * ( 3 3310 )0 * (3 33100)4.

那么代码就可以很轻松地写出来了

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define ri register int 
#define ll long long 
ll b,t,h,P;
char c[ 100007 ];
ll TenthPow( ll a ){
    ll ans = 1,s = a;
    while( t >= 0 ){
        ll cnt = c[ t ] - '0',cur = s;
        for( ri i = 1 ; i <= cnt ; ++i )
          ans = ans * s % P;
         //和二进制快速幂不同的地方之一,请结合上面列举的拆分过程理解
        for( ri i = 1 ; i < 10 ; ++i )
          cur = cur * s % P;
        //进位
        s = cur;
        ans %= P;
        --t;
    }
    return ans;
}
int main()
{
	cin>>b>>c>>P;
	t = strlen( c );--t;//字符串读入指数,指数可能达到几十万位
	cout<<b<<"^"<<c<<" mod "<<P<<"="<<TenthPow( b );
	return 0;
}

矩阵快速幂

struct Mat {
	LL m[101][101];
};//存储结构体
Mat a,e; //a是输入的矩阵,e是输出的矩阵
Mat Mul(Mat x,Mat y) {
	Mat c;
	for(int i=1; i<=n; ++i) {
		for(int j=1; j<=n; ++j) {
			c.m[i][j] = 0;
		}
	}
	for(int i=1; i<=n; ++i) {
		for(int j=1; j<=n; ++j) {
			for(int k=1; k<=n; ++k) {
				c.m[i][j] = c.m[i][j]%mod + x.m[i][k]*y.m[k][j]%mod;
			}
		}
	}
	return c;
}
Mat pow(Mat x,LL y) { //矩阵快速幂
	Mat ans = e;
	while(y) {
		if(y&1) ans = Mul(ans,x);
		x = Mul(x,x);
		y>>=1;
	}
	return ans;
}

 

比如斐波那契

矩阵快速幂是用来求解递推式的,所以第一步先要列出递推式:

 f(n)=f(n-1)+f(n-2)

第二步是建立矩阵递推式,找到转移矩阵:

,简写成T * A(n-1)=A(n),T矩阵就是那个2*2的常数矩阵,而

这里就是个矩阵乘法等式左边:1*f(n-1)+1*f(n-2)=f(n);1*f(n-1)+0*f(n-2)=f(n-1);

这里还是说一下构建矩阵递推的大致套路,一般An与A(n-1)都是按照原始递推式来构建的,当然可以先猜一个An,主要是利用矩阵乘法凑出矩阵T,第一行一般就是递推式,后面的行就是不需要的项就让与其的相乘系数为0。矩阵T就叫做转移矩阵(一定要是常数矩阵),它能把A(n-1)转移到A(n);然后这就是个等比数列,直接写出通项:此处A1叫初始矩阵。所以用一下矩阵快速幂然后乘上初始矩阵就能得到An,这里An就两个元素(两个位置),根据自己设置的A(n)对应位置就是对应的值,按照上面矩阵快速幂写法,res[1][1]=f(n)就是我们要求的。

给一些简单的递推式
1.f(n)=a*f(n-1)+b*f(n-2)+c;(a,b,c是常数)

2.f(n)=c^n-f(n-1) ;(c是常数)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
 
const int M = 1e9+7;
 
struct Matrix {
	long long a[2][2];
	Matrix() {
		memset(a, 0, sizeof(a));
	}
	Matrix operator * (const Matrix y) {
		Matrix ans;
		for(int i = 0; i <= 1; i++)
			for(int j = 0; j <= 1; j++)	
				for(int k = 0; k <= 1; k++)	
					ans.a[i][j] += a[i][k]*y.a[k][j];
		for(int i = 0; i <= 1; i++)
			for(int j = 0; j <= 1; j++)
				ans.a[i][j] %= M;
		return ans;
	}
	void operator = (const Matrix b) {
		for(int i = 0; i <= 1; i++)
			for(int j = 0; j <= 1; j++)
				a[i][j] = b.a[i][j];
	}
};
 
int solve(long long x) {
	Matrix ans, trs;
	ans.a[0][0] = ans.a[1][1] = 1;//斐波那契初始值
	trs.a[0][0] = trs.a[1][0] = trs.a[0][1] = 1;//功能矩阵
	while(x) {
		if(x&1) 
			ans = ans*trs;
		trs = trs*trs;
		x >>= 1;
	}
	return ans.a[0][0];
}
 
int main() {
	int n;
	scanf("%d", &n);
	cout << solve(n-1) << endl;
	return 0;
}

逆元(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)扩展欧几里得算法求逆元

扩展欧几里得算法可以参考小白书;

百度百科-乘法逆元中有这样一个例子:

例如:4关于1模7的乘法逆元为多少?

4X≡1 mod 7

这个方程等价于求一个X和K,满足

4X=7K+1

其中X和K都是整数。

求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为质数)

即i*p%m=1

复杂度:O(N)

#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define mem(a,num) memset(a,num,sizeof(a))
const int maxn = 1e5+5;

ll t,n,temp,ans;
ll num[maxn];
const int mod = 1000000009;
ll inv[maxn];

int main() {inv[1] = 1;
	for(int i = 2; i < 10000; i++) {
		inv[i] = inv[mod % i] * (mod - mod / i) % mod;
		cout<<i<<" "<<inv[i]<<endl;
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值