快速幂/快速乘/矩阵快速幂
快速幂
简介
快速幂(Binary Exponentiation),是一个在 \(O(\log b)\) 的时间内计算 \(a^b\) 的小技巧,而暴力计算复杂度则为 \(O(n)\)
设 \(b\) 在二进制下表示为:
于是有:
所以可以通过 \(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\) 种选择,所以答案为
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\) 以二进制的形式表示,可以得到:
这样做的好处是,运算过程中每一步的结果都不超过 \(2\times 10^{18}\) ,仍然在 long long
的范围内,所以可以通过 \(k\) 次递推求出每个乘积项,并把它们累加到答案中,时间复杂度为 \(O(\log b)\)
模板
#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\) 的矩阵,并且有:
矩阵乘法满足结合律,即 \((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\) 次方,可以用类似快速幂的方法降低时间复杂度
模板
给定 \(n,m\) ,求 \(F_n \bmod m\) ,其中 \(F\) 表示斐波那契数列
#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\)
#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}\) ,所以把转移需要用到的所有项都放入状态矩阵:
#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\) 列转移而来,转移矩阵就为:
#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;
}