快速幂引申
快速幂原理
快速幂是指能在 \(\log n\) 的时间复杂度内完成计算 \(a^n\) 的思想。
long long pow(long long a, long long b){ long long res=1; while(b){ if(b&1)res*=a; a*=a; b>>= 1; } return res; }
下面讲几个关于快速幂的引申拓展。
\(i.\)模意义下的快速幂
前置知识:费马小定理。
若 \(p\) 为素数,\(\gcd(a,p)=1\) ,则 \(a^{p-1}\equiv 1\pmod{p}\)。
另一个形式:对于任意整数 \(a\) ,有 \(a^p \equiv a \pmod{p}\)。
意味着我们可以将次数 \(n\) 对于 \(mod-1\) 取模,得到的结果即是我们所需要的优化幂次下的快速幂。
long long pow(long long a, long long b){
b=b%mod;
long long res=1;
while(b){
if(b&1)res*=a,res%=mod;
a*=a;
a%=mod;
b>>= 1;
}
return res%mod;
}
可以解决幂次过大以及模数极小情况下的极高效计算。
\(ii.\) 矩阵乘法加速意义下的运算
前置知识:矩阵。
我们可以将一个递推式转化为一个矩阵乘法的形式,如斐波那契数列 \(F_n =F_{n-1}+F_{n-2}\) ,我们将其写成
于是我们有\(
\lbrack\begin{bmatrix}
F_{n} & F_{n-1} \\
\end{bmatrix}
\rbrack\)=\(\lbrack
\begin{bmatrix}
F_2 & F_1 \\
\end{bmatrix}
\rbrack\cdotp\lbrack
\begin{bmatrix}
1 & 1 \\
1 & 0 \\
\end{bmatrix}\rbrack^{n-2}
\)
这个形式很熟悉了,就是我们一直用的快速幂,可以在 \(\log n\) 的时间复杂度计算 \(F_n\)。
对于一些简单的dp式,我们也可以将其转化为矩乘的形式以压缩时间。
如
等价于
例题
问题描述:
对于\(k(k>0)\)有
求\(\mod m\)意义下\(a_n,b_n,c_n\),\(n<10^{16}\)。
大体思路:
标记状态
表示第\(k+2\)天\((k>0)\)
显然有
记
我们有
最终,我们需要的\((n-2)+2\)天就是
显然可以求解,复杂度为\(\log n\) (显然我们有一个巨大的常数,但这并不影响,因为\(\log n\)不超过\(100\))。
代码实现:
#include<iostream>
#define ll long long
struct Kx{
ll x[11];
};
struct K_{
ll x[11][11];
void clear(){
for(int i=0;i<11;i++)
for(int j=0;j<11;j++)
x[i][j]=0;
}
};
K_ operator*(K_ v) const {
//这里是重载
}
Kx operator*(K_ v) const {
//这里是重载
}
const Kx K_0={3,1,3,1,3,1,0,1,1,0,1};
const K_ K=
{
{p , 1 , 1 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0},
{q , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0},
{1 , 0 , u , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0},
{0 , 0 , v , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0},
{1 , 0 , 1 , 0 , x , 1 , 0 , 0 , 0 , 0 , 0},
{0 , 0 , 0 , 0 , y , 0 , 0 , 0 , 0 , 0 , 0},
{1 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0},
{0 , 0 , 1 , 0 , 0 , 0 , 0 , w , 0 , 0 , 0},
{0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , z , 0 , 0},
{t , 0 , 0 , 0 , 1 , 0 , 2r , 0 , 0 , 1 , 0},
{1 , 0 , 0 , 0 , 2 , 0 , r , 0 , 0 , 1 , 1},
};
K_ pow(Kx K0,ll n);
ll n,m;
ll p,q,r,t;
ll u,v,w;
ll x,y,z;
int main(){
scanf("%d%d",&n,&m);
scanf("%d%d%d%d",&p,&q,&r,&t);
scanf("%d%d%d",&u,&v,&w);
scanf("%d%d%d",&x,&y,&z);
Kx Kn=K_0*pow(K,n);
printf("nodgd %d\nCiocio %d\nNicole%d",Kn.x[0],Kn.x[2],Kn.x[4]);
return 0;
}
K_ pow(Kx K0,ll n){
//题目没有说明n与m互质,故不能使用费马小定理
K_ k=K;
K_ ans;
ans.clear();
while(n){
if(n&1ll)ans*=k;
k*=k;
n>>=1;
}
return ans;
}
于是我们便可以处理极大数时的dp并且有着不错的时间。

浙公网安备 33010602011771号