ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-02-22 16:04:31  阅读:275  来源: 互联网

标签:le 广义 矩阵 infin bmatrix otimes 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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有