矩阵优化

[[做题记录]]

矩阵乘法的一些性质:
乘法结合律: (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\) 次方。

posted @ 2023-11-03 09:30  星影流灿  阅读(43)  评论(0)    收藏  举报