快速幂引申

快速幂原理

快速幂是指能在 \(\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_{n-1} & F_{n-2} \\ \end{bmatrix} \rbrack\cdotp\lbrack \begin{bmatrix} 1 & 1 \\ 1 & 0 \\ \end{bmatrix} \rbrack \]

于是我们有\( \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式,我们也可以将其转化为矩乘的形式以压缩时间。

\[dp_i =3dp_{i-1}+7dp_{i-2}+13 \]

等价于

\[\lbrack \begin{bmatrix} dp_{n} & dp_{n-1} & 1\\ \end{bmatrix} \rbrack=\lbrack \begin{bmatrix} dp_{n-1} & dp_{n-2} & 1\\ \end{bmatrix} \rbrack\cdotp \begin{bmatrix} 3 & 1 & 0\\ 7 & 0 & 0\\ 13 & 0 & 1 \\ \end{bmatrix} \]

例题

问题描述:

\[a_{1}=b_{1}=c_{1}=1 \]

\[a_{2}=b_{2}=c_{2}=3 \]

对于\(k(k>0)\)

\[a_{k+2}=pa_{k+1}+qa_k+b_{k+1}+c_{k+1}+rk^2+tk+1 \]

\[b_{k+2}=ub_{k+1}+vb_k+a_{k+1}+c_{k+1}+w^k \]

\[c_{k+2} = xc_{k+1}+yc_k + a_{k+1} + b_{k+1} + z^k+k+2 \]

\(\mod m\)意义下\(a_n,b_n,c_n\)\(n<10^{16}\)

大体思路:

标记状态

\[K_k= \lbrack \begin{bmatrix} a_{k+2} & a_{k+1} & b_{k+2} & b_{k+1} & c_{k+2} & c_{k+1} & rk^2 & w^k & z^k & k & 1 \end{bmatrix} \rbrack \]

表示第\(k+2\)\((k>0)\)
显然有

\[K_0=\lbrack \begin{bmatrix} 3 & 1 & 3 & 1 & 3 & 1 & 0 & 1 & 1 & 0 & 1 \end{bmatrix}\rbrack \]

\[K=\begin{bmatrix} 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 \\ \end{bmatrix} \]

我们有

\[K_k=K_{k-1}\cdotp K \]

最终,我们需要的\((n-2)+2\)天就是

\[K_0\cdotp K^{n-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并且有着不错的时间。

posted @ 2025-08-17 14:03  exenthcig  阅读(17)  评论(0)    收藏  举报