其他分享
首页 > 其他分享> > 「笔记」广义矩阵乘法与动态 Dp

「笔记」广义矩阵乘法与动态 Dp

作者:互联网

目录

写在前面

前置知识:DP矩阵乘法倍增线段树

最大子段和和 GSS1 的题解区还没有下面这种做法,你们快上啊(

广义矩阵乘法

对于一 \(p\times m\) 的矩阵 \(A\),与 \(m\times q\) 的矩阵 \(B\),定义广义矩阵乘法 \(A\times B = C\) 的结果是一个 \(p\times q\) 的矩阵 \(C\),满足:

\[C_{i,j} = (A_{i, 1}\otimes B_{1,j}) \oplus (A_{i,2}\otimes B_{2,j})\oplus \cdots \oplus (A_{i, n}\otimes B_{n,j}) \]

其中 \(\oplus\) 与 \(\otimes\) 是两种二元运算。

考察这种广义矩阵乘法是否满足结合律:

\[\begin{aligned} ((AB)C)_{i,j} &= \bigoplus_{k=1}^{p}(AB)_{i,k}\otimes C_{k,j}\\ &= \bigoplus_{k=1}^{p}\left( \left(\bigoplus_{t=1}^{q} A_{i,t}\otimes B_{t,k}\right) \otimes C_{k,j}\right)\\ (A(BC))_{i,j} &= \bigoplus_{t=1}^{q}A_{i,t}\otimes (BC)_{t,j}\\ &= \bigoplus_{t=1}^{q} \left(A_{i,t}\otimes \left(\bigoplus_{k=1}^{p} B_{t,k} \otimes C_{k,j}\right)\right) \end{aligned}\]

显然,若 \(\otimes\) 运算满足交换律、结合律,且 \(\otimes\) 对 \(\oplus\) 存在分配律,即存在 \(\left(\bigoplus a\right)\otimes b = \bigoplus \left( a\otimes b \right)\) 时,广义矩阵乘法满足结合律。即根据上述运算规律,对二式进行 \(\oplus\) 的交换后有:

\[((AB)C)_{i,j} = (A(BC))_{i,j} = \bigoplus_{k=1}^{p}\bigoplus_{t=1}^{q} A_{i,t}\otimes B_{t,k}\otimes C_{k,j} \]

维护 DP

P1115 最大子段和 为例。

给定一个长度为 \(n\) 的数列 \(a\),选出其中连续且非空的一段使得这段和最大。
\(1\le n\le 2\times 10^5\),\(-10^4\le a_i\le 10^4\)。
1S,128MB。

记 \(f_i\) 表示以 \(a_i\) 结尾的最大子段和,初始化 \(f_0 = -\infin\)。转移时考察是否要加上前面一段的贡献。前面一段的最大贡献为 \(f_{i-1}\)。则显然有:

\[f_{i} = \max (f_{i - 1} + a_i, a_i) \]

定义 \(g\) 为 \(f\) 的前缀最大值,答案即为 \(g_n\)。算法总时间复杂度 \(O(n)\) 级别。


考虑加法运算运算与取 \(\max\) 运算的性质:发现取 \(\max\) 满足交换律与结合律,且加法对取 \(\max\) 满足分配率,即有:

\[a + \max_{i}(b_i) = \max_{i}(a + b_i) \]

考虑定义一种广义矩阵乘法,满足:

\[C_{i,j} = \max_{k}\left( A_{i,k} +B_{k,j}\right) \]

考虑将上述状态转移方程写成广义矩阵乘法形式。当从 \(i-1\) 转移到 \(i\) 时,显然有:

\[\begin{bmatrix} f_{i-1}& g_{i-2}& 0 \end{bmatrix} \times \begin{bmatrix} a_i& 0& -\infin\\ -\infin& 0& -\infin\\ a_i& -\infin& 0\\ \end{bmatrix} = \begin{bmatrix} f_i& g_{i-1}& 0 \end{bmatrix}\]

根据上述分析,显然该运算满足结合律,则有:

\[\begin{bmatrix} -\infin& -\infin& 0 \end{bmatrix} \times \prod_{i=1}^{n} \begin{bmatrix} a_i& 0& -\infin\\ -\infin& 0& -\infin\\ a_i& -\infin& 0\\ \end{bmatrix} = \begin{bmatrix} f_{n}& g_{n-1}& 0 \end{bmatrix}\]

其中 \(\prod\) 表示连续广义矩阵乘法。预处理整个序列的广义矩阵乘积后,根据上式可得 \(f_n\) 与 \(g_{n-1}\),取最大值即得答案。总复杂度 \(O\left(3^3\times n\right)\) 级别。

静态区间查询

SP1043 GSS1 - Can you answer these queries I

给定一个长度为 \(n\) 的数列 \(a\),给定 \(m\) 次询问。
每次询问给定区间 \([l,r]\),要求选出区间 \([l,r]\) 中连续且非空的一段使得这段和最大,输出最大子段和。
\(1\le n\le 5\times 10^4\),\(-15007\le a_i\le 15007\)。
原题面中并没有给出 \(m\) 的范围,此处根据实际测试情况推断 \(m\) 与 \(n\) 同阶。
230ms,1.46G。

发现上述题目中广义矩阵乘法做法 复杂度比直接做还劣 有着很好的扩展性。对于任意区间,预处理区间对应的广义矩阵乘积后即得该区间的最大子段和。

问题变为如何快速求得区间广义矩阵乘积。广义矩阵乘法满足结合律,且本题中没有修改操作,考虑对于每个位置 \(i\) 预处理以 \(i\) 为左端点的长度为 \(2\) 的幂的区间的广义矩阵乘积。回答询问时倍增拼凑区间即可。总时间复杂度 \(O\left(3^3 (n+m)\log n\right)\) 级别。

不用维护一堆乱七八糟的玩意,个人认为比隔壁直接上线段树好写(

//知识点:矩阵乘法,倍增
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 5e4 + 10;
const int kL = 3;
const LL kInf = 1e9 + 2077;
//=============================================================
int n, m;
struct Matrix {
  LL a[kL][kL];
  Matrix() {
    memset(a, 0, sizeof (a));
  }
  void build() {
    for (int i = 1; i <= kL; ++ i) a[i][i] = 1;
  }
  Matrix operator * (const Matrix &b_) const {
    Matrix ret;
    memset(ret.a, 128, sizeof (ret.a));
    for (int k = 0; k < kL; ++ k) {
      for (int i = 0; i < kL; ++ i) {
        for (int j = 0; j < kL; ++ j) {
          ret.a[i][j] = std::max(ret.a[i][j], a[i][k] + b_.a[k][j]);
        }
      }
    }
    return ret;
  }
} f[kN][21];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
LL Query(int l, int r) {
  Matrix ans;
  ans.a[0][0] = ans.a[0][1] = -kInf;
  for (int i = 20; i >= 0; -- i) {
    if (l + (1 << i) - 1 <= r) {
      ans = ans * f[l][i];
      l += (1 << i);
    }
  }
  return std::max(ans.a[0][0], ans.a[0][1]);
}
//=============================================================
int main() {
  n = read();
  for (int i = 1; i <= n; ++ i) {
    f[i][0].a[0][0] = f[i][0].a[2][0] = read();
    f[i][0].a[1][0] = f[i][0].a[2][1] = f[i][0].a[0][2] = f[i][0].a[1][2] = -kInf;
  }
  for (int i = 1; i <= 20; ++ i) {
    for (int j = 1; j + (1 << i) - 1 <= n; ++ j) {
      f[j][i] = f[j][i - 1] * f[j + (1 << (i - 1))][i - 1];
    }
  }
  m = read(); 
  for (int i = 1; i <= m; ++ i) {
    int l = read(), r = read();
    printf("%lld\n", Query(l, r));
  }
  return 0;
}

动态区间查询

SP1043 GSS1 - Can you answer these queries I

给定一个长度为 \(n\) 的数列 \(a\),给定 \(m\) 次询问。
每次询问给定区间 \([l,r]\),要求选出区间 \([l,r]\) 中连续且非空的一段使得这段和最大,输出最大子段和。
\(1\le n,m\le 5\times 10^4\),\(-10^4\le a_i\le 10^4\)。
330ms,1.46G。

在上题的基础上加入了单点修改操作。发现每次修改仅会影响对应位置的矩阵,以及包含该位置的区间的广义矩阵乘积,考虑线段树维护广义矩阵乘积,每次修改仅需更新自叶到根的 \(\log n\) 个位置的对应区间。总时间复杂度 \(O\left(3^3 (n+m)\log n\right)\)。

gugugu

动态树形 DP

gugugu

例题

gugugu

写在最后

鸣谢:

矩阵 - OI Wiki
动态 DP - OI Wiki
洛谷日报#130[GKxx]动态DP入门 - GKxx

标签:le,广义,矩阵,infin,bmatrix,otimes,Dp,乘法
来源: https://www.cnblogs.com/luckyblock/p/14430820.html