二分幂,快速幂,矩阵快速幂在算大指数次方时是很高效的。
求 a^n 的值是多少?n是1到10^18次方的一个整数。
求一个数的n次方,朴素的算法就是直接for循环,一遍一遍的乘,a*a*a*a*a*a… …,O(N)的复杂度。此时,如果n很小的话,并没有什么影响。
但是当n非常大,n=10^18,O(N)也会超时,那么需要更快的算法,二分幂算法 和 快速幂算法。
而对于矩阵的高次幂,则将快速幂算法改进为矩阵快速幂算法。
最后讲一下,在大数情况下的乘法。
要求 a^n,如果知道了 a^(n/2) 次方的话,再来个平方就可以了。
即
如果n是偶数,则A=a^(n/2) ; A=A*A.。
如果n是奇数 , 则A=a^((n-1)/2) ; A=a*A*A。
这就一下子差不多就节省了n/2-1次乘法运算
那么按照这个思路就能运用分治的思想,那么复杂度就有原来的O(n),降低为O(lgn)。
long long int pow(int a,int n)//求a的n次幂
{
if (n==0)
return 1;
if (n==1)
return a;
long long int ans=pow(a,n/2);//从函数的功能区理解递归
ans*=ans;
if (n%2==1)
ans*=a;
return ans;
}
快速幂 , 矩阵快速幂 在算大指数次方时是很高效的,他的基本原理是二进制。快速乘也是用了二进制。
大家首先要认识到这一点:任何一个整数N,都能用二进制来表示。。
那么对于a^n , n一定可以用二进制表示。
比如a^156,而156(10)=10011100(2)
那么
A= a156=a10011100
= a27∗1+26∗0+25∗0+24∗1+23∗1+22∗1+21∗0+20∗0
= (a27∗1)∗(a26∗0)∗(a25∗0)∗(a24∗1)∗(a23∗1)∗(a22∗1)∗(a21∗0)∗(a20∗0)
我们就按照这个公式来求解 a156 ,原来要进行156-1=155次乘法运算,现在的差不多运算次数就是他 二进制的长度*二进制中1的个数=8*4=24次
long long int fun( int a, int b )
{
long long int r = 1;
int base = a;
while( b != 0 )
{
if(b & 1)//判断奇偶性
{
r *= base;
}
base *= base; //注意:a^{2^7}=a^{2^6} * a^{2^6} ,而不是 a^{2^7}=a^{2^6} * a ,所以这是对的。
b /= 2;//与b=b>>1相同
}
return r;
}
可能你会问了这个算法有什么用呢?其实用的更多是使用矩阵快速幂,算递推式,注意是递推式 ,比如 f(n)=a*f(n-1)+b*f(n-2),简单的如斐波那契数列的第一亿项的结果模上10000000后是多少你还能用递推式去,逐项递推吗?当然不能,这里就可以发挥矩阵快速幂的神威了,那斐波那契数列和矩阵快速幂能有一毛钱的关系?答案是有而且很大
对于f(n)=a*f(n-1)+b*f(n-2) ,
我们可以考虑矩阵这种数学工具,构造矩阵
这样求f(n),f(n-1) 就相当于求左边矩阵的n-2次幂。这个时候就可以用上面的快速幂来计算了。
代码与快速幂类似,只是实数乘法变成了矩阵乘法。用个函数写就行了。
粘贴一个求斐波那契数列f(n) 的代码
# include
# include
using namespace std;
#define NUM 50
int MAXN,n,mod;
struct Matrix//矩阵的类
{
int a[NUM][NUM];
void init() //将其初始化为单位矩阵
{
memset(a,0,sizeof(a));
for(int i=0;ia[i][i]=1;
}
} A;
Matrix mul(Matrix a,Matrix b) //(a*b)%mod 矩阵乘法
{
Matrix ans;
for(int i=0;ifor(int j=0;ja[i][j]=0;
for(int k=0;ka[i][j]+=a.a[i][k]*b.a[k][j];
ans.a[i][j]%=mod;
}
return ans;
}
Matrix add(Matrix a,Matrix b) //(a+b)%mod //矩阵加法
{
int i,j,k;
Matrix ans;
for(i=0;ifor(j=0;ja[i][j]=a.a[i][j]+b.a[i][j];
ans.a[i][j]%=mod;
}
return ans;
}
Matrix pow(Matrix a,int n) //(a^n)%mod //矩阵快速幂
{
Matrix ans;
ans.init();
while(n)
{
if(n%2)//n&1
ans=mul(ans,a);
n/=2;
a=mul(a,a);
}
return ans;
}
Matrix sum(Matrix a,int n) //(a+a^2+a^3....+a^n)%mod// 矩阵的幂和
{
int m;
Matrix ans,pre;
if(n==1)
return a;
m=n/2;
pre=sum(a,m); //[1,n/2]
ans=add(pre,mul(pre,pow(a,m))); //ans=[1,n/2]+a^(n/2)*[1,n/2]
if(n&1)
ans=add(ans,pow(a,n)); //ans=ans+a^n
return ans;
}
void output(Matrix a)//输出矩阵
{
for(int i=0;ifor(int j=0;j"%d%c",a.a[i][j],j==MAXN-1?'\n':' ');
}
int main()
{
freopen("in.txt","r",stdin);
Matrix ans;
scanf("%d%d%d",&MAXN,&n,&mod);
for(int i=0;ifor(int j=0;j"%d",&A.a[i][j]);
A.a[i][j]%=mod;
}
ans=sum(A,n);
output(ans);
return 0;
}
求a*b%m , 当a*b结果很大,乘完后可能会移除。
可以用二进制来实现快速乘算法。
以前十进制的乘法是: 123*567=123*5*100 + 123*6*10 + 123 * 7 * 1
这里100,10, 100 都是十进制 的进制位数。那么如果考虑二进制的话,我们任选其他任意二进制数,就有
1001101∗11010=1001101∗24∗1+1001101∗23∗1+1001101∗22∗0+1001101∗21∗1+1001101∗20∗0
我们对上面的每一个加项进行取模,在加起来,就不会溢出了。
long long int fun(long long int a ,long long int b , long long int m)
{
int sum=0;
int k=1;
while(b)
{
if(b&1)
{
sum=(sum+a*k)%m;
}
k=(k*2)%m;
b=b/2;
}
}
引用原句
在我就说下我对二进制的感想吧:
我们在做很多”连续“的问题的时候都会用到二进制将他们离散简化
1.多重背包问题
2.树状数组
3.状态压缩DP
……………还有很多。。。究其根本还是那句话:化连续为离散。。很多时候我们并不是为了解决一个问题而使用二进制,更多是时候是为了优化而使用它。所以如果你想让你的程序更加能适应大数据的情况,那么学习学习二进制及其算法思想将会对你有很大帮助。