矩阵优化
[[做题记录]]
矩阵乘法的一些性质:
乘法结合律: (AB)C=A(BC).
乘法左分配律:(A+B)C=AC+BC
乘法右分配律:C(A+B)=CA+CB
P1939 矩阵加速(数列)
这里,我们定义目标矩阵为
\[A_n =
\begin{bmatrix}
a_n \\
a_{n-1} \\
a_{n-2} \\
\end{bmatrix}
\]
那么我们思考一下它怎么从 \(A_{n - 1}\) 推导而来
\[A_{n-1} =
\begin{bmatrix}
a_{n - 1} \\
a_{n - 2} \\
a_{n - 3} \\
\end{bmatrix}
\]
由题有:
\[\begin{cases}
a_n = 1 \times a_{n-1} + 0 \times a_{n-2} + 1 \times a_{n-3} \\
a_{n-1} = 1\times a_{n-1} + 0 \times a_{n - 2} + 0 \times a_{n - 3} \\
a_{n-2} = 0\times a_{n-1} + 1 \times a_{n - 2} + 0 \times a_{n - 3}
\end{cases}
\]
所以我们的转移矩阵就是
\[\begin{bmatrix}
1 & 0 & 1 \\
1 & 0 & 0 \\
0 & 1 & 0
\end{bmatrix}
\]
注意:矩阵乘法一般不满足分配律,所以相乘时顺序不要搞错。
\[A_4 = A_3 * T, A_5 = A_3 * T^2
\]
code:
#include<bits/stdc++.h>
using namespace std;
#define mo 1000000007
#define int long long
struct juzhen{
int a[5][5];
int n, m;
juzhen(){ //矩阵初始化
n = m = 0;
fill(a[0], a[0] + 5 * 5, 0);
}
void unit(int kn){ //构建单位矩阵
n = m = kn;
for(int i = 1; i <= n; i ++) a[i][i] = 1;
}
void init(){ //对于每个题目的初始矩阵
n = 3, m = 1;
a[1][1] = a[2][1] = a[3][1] = 1;
}
void zy(){ //每个题目的转移矩阵
n = 3, m = 3;
a[1][1] = a[1][3] = a[2][1] = a[3][2] = 1;
}
void out(){ //输出矩阵
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
cout << a[i][j] << " ";
}cout << endl;
}
}
juzhen operator *(const juzhen &b){
juzhen res;
res.n = n, res.m = b.m;
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= b.m; j ++){
for(int k = 1; k <= m; k ++){
res.a[i][j] = (res.a[i][j] + a[i][k] * b.a[k][j] % mo) % mo;
}
}
}
return res;
}
};
juzhen qsm(juzhen base, int k){
juzhen res;
res.unit(3);
while(k){
if(k & 1) res = res * base;
base = base * base;
k >>= 1;
}
return res;
}
int n;
signed main(){
//freopen("shuju.in", "r", stdin);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int TN;
cin >> TN;
while(TN--){
cin >> n;
if(n <= 3) cout << 1 << endl;
else{
juzhen ans, t;
ans.init();
t.zy();
// t.out();
// ans.out();
ans = qsm(t, n - 3) * ans;
cout << ans.a[1][1] << endl;
}
}
return 0;
}
P1349 广义斐波那契数列
一眼矩乘。
考虑目标矩阵为
\[A_n = [a_{n}, a_{n-1}]
\]
由矩阵
\[A_{n-1} = [a_{n-1}, a_{n-2}]
\]
转移而来
又
\[\begin{cases}
a_{n} = p \times a_{n - 1} + q \times a_{n-2} \\
a_{n-1} = 1 \times a_{n - 1} + 0 \times a_{n-2}
\end{cases}
\]
所以我们就可以令转移矩阵为
\[T =
\begin{bmatrix}
p & 1 \\
q & 0
\end{bmatrix}
\]
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int p, q, a1, a2, n, mo;
struct juzhen{
int a[5][5];
juzhen(){
memset(a, 0, sizeof a);
}
void unit(){
for(int i = 1; i <= 2; i ++) a[i][i] = 1;
}
juzhen operator *(juzhen b){
juzhen res;
for(int i = 1; i <= 2; i ++){
for(int j = 1; j <= 2; j ++){
for(int k = 1; k <= 2; k ++){
res.a[i][j] = (res.a[i][j] + (a[i][k] % mo) * b.a[k][j] % mo) % mo;
}
}
}
return res;
}
};
juzhen qsm(juzhen base, int k){
juzhen res;
res.unit();
while(k){
if(k & 1) res = res * base;
base = base * base;
k >>= 1;
}
return res;
}
void init(juzhen &s, juzhen &t){
s.a[1][1] = a2, s.a[1][2] = a1;
t.a[1][1] = p, t.a[1][2] = 1, t.a[2][1] = q, t.a[2][2] = 0;
}
signed main(){
//freopen("shuju.in", "r", stdin);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> p >> q >> a1 >> a2 >> n >> mo;
if(n == 1) cout << a1;
else if(n == 2) cout << a2;
else{
juzhen f3, t, ans;
init(f3, t);
ans = f3 * qsm(t, n - 2);
cout << ans.a[1][1];
}
return 0;
}
P2233 [HNOI2002] 公交车路线
一个矩阵优化的小技巧捏。这道题看着是个dp,但实际用矩阵非常简单。我们的转移矩阵就是邻接矩阵。为什么?我们仿照dp,令 \(f[i][j]\) 表示从 \(i\) 到 \(j\) 的方案数,那么从 \(i\) 到 \(j\) 只需要枚举中间的中转站 \(k\) 转移即可。那么就是
\[f[i][j] = \sum_{k=1}^8 f[i][k] \times f[k][j]
\]
就会发现这就是矩阵乘法。因此直接写矩乘就好了。
#include<bits/stdc++.h>
using namespace std;
#define mo 1000
struct juzhen{
int a[10][10];
juzhen(){
memset(a, 0, sizeof a);
}
void unit(){
for(int i = 1; i <= 8; i ++) a[i][i] = 1;
}
void make_zy(){
a[1][2] = a[1][8] = 1;a[2][1] = a[2][3] = 1; a[3][2] = a[3][4] = 1;
a[4][3] = a[4][5] = 1;a[6][5] = a[6][7] = 1;
a[7][6] = a[7][8] = 1;a[8][7] = a[8][1] = 1;
}
void out(){
for(int i = 1; i <= 8; i ++){
for(int j = 1; j <= 8; j ++){
cout << a[i][j] << " ";
}cout << endl;
}
}
};
juzhen operator *(const juzhen &a, const juzhen &b){
juzhen c;
for(int i = 1; i <= 8; i ++){
for(int j = 1; j <= 8; j ++){
for(int k = 1; k <= 8; k ++){
c.a[i][j] = (c.a[i][j] + a.a[i][k] * b.a[k][j] % mo) % mo;
}
}
}
return c;
}
juzhen qsm(juzhen base, int k){
juzhen res;
res.unit();
while(k){
if(k & 1) res = res * base;
base = base * base;
k >>= 1;
}
return res;
}
signed main(){
// freopen("sr.in", "r", stdin);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
juzhen bg, tmp;
tmp.make_zy();
cout << qsm(tmp, n).a[1][5] << endl;
return 0;
}
Sasha and Array
线段树优化矩阵。设矩阵 \(A\) 表示线段树左区间和的矩阵,\(B\) 表示线段树右区间和的矩阵,那么查询答案时, return \(A + B\) 即可。修改,将 \(A\) 区间加上 \(x\) ,因为矩阵满足左分配律,就是将 \(A \times\) 转移矩阵的 \(x\) 次方。

浙公网安备 33010602011771号