快速幂/快速乘/矩阵快速幂

快速幂

简介

快速幂(Binary Exponentiation),是一个在 \(O(\log b)\) 的时间内计算 \(a^b\) 的小技巧,而暴力计算复杂度则为 \(O(n)\)

\(b\) 在二进制下表示为:

\[b=c_{k-1}2^{k-1}+c_{k-2}2^{k-2}+\cdots+c_02^0 \]

于是有:

\[a^b=a^{2^{k-1}c_{k-1}}\cdot a^{2^{k-2}c_{k-2}}\cdots a^{2^0c_0} \]

所以可以通过 \(k\) 次递推求出每个乘积项,并把它们累积到答案中

模板

int qpow(int a, int b, int p)
{
    int res = 1 % p;
    for(; b; b >>= 1) {
        if(b & 1)
            res = 1ll * res * a % p;
       	a = 1ll * a * a % p;
    }
    return res;
}

题目

NOIP2013 转圈游戏

根据题意,所求的位置编号即为 \((x+m\times 10^k)\bmod n\) ,用快速幂求 \(10^k\) 即可

int n, m, k, x;
cin >> n >> m >> k >> x;
cout << (x + qpow(10, k, n) * m % n) % n << endl;
HNOI2008 越狱

可以发现正面求解并不容易,于是考虑反面。

总状态数为 \(m^n\) ,考虑求相邻房间犯人都信仰不同宗教的状态,第一个房间可以任意信仰一个宗教,有 \(m\) 种选择,而之后的每个房间犯人都不能与前一个房间的犯人信仰同一个宗教,有 \(m-1\) 种选择,所以答案为

\[m^n-m\times (m-1)^{n-1} \]

ll m, n;
cin >> m >> n;
cout << (MOD + qpow(m, n, MOD) - m * qpow(m - 1, n - 1, MOD) % MOD) % MOD << endl;

快速乘

简介

快速乘,解决的是两个 \(10^{18}\) 数量级的数相乘取模的问题,即求 \(a\times b\bmod p\) ,其中 \(1\leq a,b,p\leq 10^{18}\)

如果直接相乘取模必然会溢出,所以类似快速幂的思想,我们同样把 \(b\) 以二进制的形式表示,可以得到:

\[a\times b=c_{k-1}\cdot a\cdot2^{k-1}+c_{k-2}\cdot a\cdot 2^{k-2}+\cdots+c_0\cdot a\cdot 2^0 \]

这样做的好处是,运算过程中每一步的结果都不超过 \(2\times 10^{18}\) ,仍然在 long long 的范围内,所以可以通过 \(k\) 次递推求出每个乘积项,并把它们累加到答案中,时间复杂度为 \(O(\log b)\)

模板

AcWing 90

#define ll long long
ll mul(ll a, ll b, ll p)
{
    ll res = 0;
    for(; b; b >>= 1) {
        if(b & 1)
            res = (res + a) % p;
        a = a * 2 % p;
    }
    return res;
}

矩阵快速幂

简介

先给出矩阵乘法的定义:设 \(A\)\(n\times m\) 的矩阵, \(B\)\(m\times p\) 的矩阵,则 \(C=A\times B\)\(n\times p\) 的矩阵,并且有:

\[C_{i,j}=\sum_{k=1}^m A_{i,k}\times B_{k,j} \]

矩阵乘法满足结合律,即 \((A\times B)\times C=A\times(B\times C)\) ,满足分配律,即 \((A+B)\times C=A\times C+B\times C\) ,但不满足交换律

在矩阵乘法中有一种特殊的情形, \(F\)\(1\times n\) 的矩阵, \(A\)\(n\times n\) 的矩阵,那么 \(F^{'}=F\times A\) 也是 \(1\times n\) 矩阵,那么把 \(F\) 称为状态矩阵,把 \(A\) 称为转移矩阵 ,矩阵相乘就可以从一个状态递推到下一个状态,递推 \(n\) 次也就是乘以转移矩阵的 \(n\) 次方,可以用类似快速幂的方法降低时间复杂度

模板

AcWing 205

给定 \(n,m\) ,求 \(F_n \bmod m\) ,其中 \(F\) 表示斐波那契数列

\[\left[\begin{array}{ccc}F_{n-1} & F_{n-2}\end{array}\right] \times \left[\begin{array}{ccc} 1 & 1 \\ 1 & 0 \end{array}\right] = \left[ \begin{array}{ccc}F_n & F_{n-1} \end{array}\right] \]

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

const int MOD = 10000;

void mul(int f[2], int a[2][2])
{
    int c[2];
    memset(c, 0, sizeof(c));
    for(int i = 0; i < 2; i++)
        for(int j = 0; j < 2; j++)
            c[i] = (c[i] + 1ll * f[j] * a[j][i]) % MOD;
    memcpy(f, c, sizeof(c));
}

void mulself(int a[2][2])
{
    int c[2][2];
    memset(c, 0, sizeof(c));
    for(int i = 0; i < 2; i++)
        for(int j = 0; j < 2; j++)
            for(int k = 0; k < 2; k++)
                c[i][j] = (c[i][j] + 1ll * a[i][k] * a[k][j]) % MOD;
    memcpy(a, c, sizeof(c));
}

int main()
{
    int n;
    while(scanf("%d", &n) != EOF && n != -1) {
        int f[2] = {0, 1};
        int a[2][2] = {{0, 1}, {1, 1}};
        for(; n; n >>= 1) {
            if(n & 1)
                mul(f, a);
            mulself(a);
        }
        printf("%d\n", f[0]);
    }
    return 0;
}

题目

LOJ 10221

题目:求斐波那契数列前 \(n\) 项和 \(S_n\bmod m\)

\[\left[\begin{array}{ccc}F_{n} & F_{n-1} & S_{n-1}\end{array}\right] \times \left[\begin{array}{ccc} 1 & 1 & 1\\ 1 & 0 & 0\\0 & 0 & 1\end{array}\right] = \left[ \begin{array}{ccc}F_{n+1} & F_{n} & S_n \end{array}\right] \]

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

int n, m;

void mul(int f[3], int a[3][3])
{
    int c[3];
    memset(c, 0, sizeof(c));
    for(int i = 0; i < 3; i++)
        for(int j = 0; j < 3; j++)
            c[i] = (c[i] + 1ll * f[j] * a[j][i]) % m;
    memcpy(f, c, sizeof(c));
}

void mulself(int a[3][3])
{
    int c[3][3];
    memset(c, 0, sizeof(c));
    for(int i = 0; i < 3; i++)
        for(int j = 0; j < 3; j++)
            for(int k = 0; k < 3; k++)
                c[i][j] = (c[i][j] + 1ll * a[i][k] * a[k][j]) % m;
    memcpy(a, c, sizeof(c));
}

int main()
{
    cin >> n >> m;
    int f[3] = {1, 0, 0};
    int a[3][3] = {{1, 1, 1}, {1, 0, 0}, {0, 0, 1}};
    for(; n; n >>= 1) {
        if(n & 1)
            mul(f, a);
        mulself(a);
    }
    cout << f[2] << endl;
    return 0;
}
LOJ 10222

题目:求 \(T_n=(F_1+2F_2+\cdots+nF_n)\bmod m\) ,其中 \(F\) 表示斐波那契数列

\(b_n=nF_n\) ,那么有 \(b_n=b_{n-1}+b_{n-2}+F_{n-1}+2F_{n-2}\) ,所以把转移需要用到的所有项都放入状态矩阵:

\[\left[\begin{array}{ccc}b_{n} & b_{n-1} & F_{n} & F_{n-1} & T_{n}\end{array}\right] \times \left[\begin{array}{ccc} 1 & 1 & 0 & 0 & 1\\ 1 & 0 & 0 & 0 & 0\\1 & 0 & 1 & 1 & 0\\2 & 0 & 1 & 0 & 0\\0 & 0 & 0 & 0 & 1\end{array}\right] = \left[ \begin{array}{ccc}b_{n+1} & b_{n} & F_{n+1} & F_n & T_n+1\end{array}\right] \]

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

int n, m;

void mul(int f[5], int a[5][5]) 
{
    int c[5];
    memset(c, 0, sizeof(c));
    for(int i = 0; i < 5; i++)
        for(int j = 0; j < 5; j++) 
            c[i] = (c[i] + 1ll * f[j] * a[j][i]) % m;
    memcpy(f, c, sizeof(c));
}

void mulself(int a[5][5])
{
    int c[5][5];
    memset(c, 0, sizeof(c));
    for(int i = 0; i < 5; i++)
        for(int j = 0; j < 5; j++)
            for(int k = 0; k < 5; k++)
                c[i][j] = (c[i][j] + 1ll * a[i][k] * a[k][j]) % m;
    memcpy(a, c, sizeof(c));
}

int main()
{
    cin >> n >> m;
    int f[5] = {1, 0, 1, 0, 0};
    int a[5][5] = {{1, 1, 0, 0, 1}, {1, 0, 0, 0, 0}, {1, 0, 1, 1, 0}, {2, 0, 1, 0, 0}, {0, 0, 0, 0, 1}};
    for(; n; n >>= 1) {
        if(n & 1)
            mul(f, a);
        mulself(a);
    }
    cout << f[4] << endl;
    return 0;
}
AcWing 226

题目:有一个名为 \(233\) 的矩阵, \(a_{0,1}=233,a_{0,2}=2333,\cdots\) ,且满足 \(a_{i,j}=a_{i-1,j}+a_{i,j-1}\) 。现在给定 \(a_{1,0},a_{2,0},\cdots,a_{n,0}\) ,求 \(a_{n,m}(n\leq 10,m\leq 10^9)\)

\(a_{i,j}=a_{i-1,j}+a_{i,j-1}\) 可得 \(a_{i,j}=a_{0,j}+\sum_{x=1}^i a_{x,j-1}\) ,由于 \(a_{0,j}=10a_{0,j-1}+3\) ,所以第 \(j\) 列的所有元素都可以由第 \(j-1\) 列转移而来,转移矩阵就为:

\[\left[\begin{array}{ccc}a_{0,j} & a_{1,j} & \cdots & a_{n,j} & 1\end{array}\right] \times \left[\begin{array}{ccc} 10 & 10 & 10 & \cdots & 10 & 0\\ 0 & 1 & 1 & \cdots & 1 & 0\\0 & 0 & 1 &\cdots & 1 & 0\\0 & 0 & 0 & 1 & 1 & 0\\0 & 0 & 0 & 0 & 1 & 0\\3 & 3 & 3 & 3 & 3 & 1\end{array}\right] = \left[ \begin{array}{ccc}a_{0,j+1} & a_{1,j+1} & \cdots & a_{n,j+1} & 1\end{array}\right] \]

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

const int MOD = 10000007;
int n, m;

void mul(int f[15], int a[15][15])
{
    int c[15];
    memset(c, 0, sizeof(c));
    for(int i = 0; i <= n + 1; i++)
        for(int k = 0; k <= n + 1; k++)
            c[i] = (c[i] + 1ll * f[k] * a[k][i]) % MOD;
    memcpy(f, c, sizeof(c));   
}

void mulself(int a[15][15])
{
    int c[15][15];
    memset(c, 0, sizeof(c));
    for(int i = 0; i <= n + 1; i++) 
        for(int j = 0; j <= n + 1; j++)
            for(int k = 0; k <= n + 1; k++)
                c[i][j] = (c[i][j] + 1ll * a[i][k] * a[k][j]) % MOD;
    memcpy(a, c, sizeof(c));
}

int main()
{
    while(scanf("%d%d", &n, &m) != EOF) {
        int f[15];
        f[0] = 23;
        for(int i = 1; i <= n; i++)
            scanf("%d", &f[i]);
        f[n + 1] = 1;
        int a[15][15];
        memset(a, 0, sizeof(a));
        for(int i = 0; i <= n; i++) {
            a[0][i] = 10;
            for(int j = 1; j <= i; j++)
                a[j][i] = 1;
            a[n + 1][i] = 3;
        }
        a[n + 1][n + 1] = 1;
        for(; m; m >>= 1) {
            if(m & 1)
                mul(f, a);
            mulself(a);
        }
        printf("%d\n", f[n]);
    }
    return 0;
}
posted @ 2022-02-26 00:00  f(k(t))  阅读(75)  评论(0编辑  收藏  举报