Pre
太棒了,今天早上的努力总算没有白费。
顺便说一句,本文内容简单,只是动态\(DP\)的基本原理(而且还不全),神犇请避让,否则会浪费时间。
原来矩阵乘法还有这么奇葩的坑(还是我太大意了),我是小看它了(话说之前有一次考试我也是用的线段树维护矩阵乘法,貌似当时代码与自己的本意不符,但是恰好是这个错误让程序违背了我的本意,最后\(A\)了,我真幸运!!!^_^)
Solution
动态\(DP\)的基本原理我就不推导了,主要提醒一下坑,在\(Conclusion\)里面,这里说一下我构造的矩阵。
\[ \left[ \begin{matrix} 0 & v_i & v_i\\ -\infty & v_i & v_i\\ -\infty & -\infty & 0 \end{matrix} \right] \]
用来计算答案的矩阵是
\[ \left[ \begin{matrix} f_{i-1}\\ g_{i-1}\\ 0 \end{matrix} \right] \]
其中的\(f_i\)表示在范围\([0,i]\)中的最大子段和(准确的说是从询问操作的开头到\(i\)的最大字段和)。
其中的\(g_i\)表示强制性以\(i\)为结尾,并且开始地方不得小于询问操作的左端点的最大子段和。
用上面的乘以下面的。
Code
#include<bits/stdc++.h>
#define xx first
#define yy second
using namespace std;
const int N = 50000 + 5;
struct Matrix {
int info[4][4];
Matrix () {
for (int i = 1; i <= 3; ++i) {
for (int j = 1; j <= 3; ++j) {
info[i][j] = INT_MIN;
}
}
}
inline void init (int u, int v, int w) {
info[1][1] = u;
info[2][1] = v;
info[3][1] = w;
for (int i = 1; i <= 3; ++i) {
info[i][3] = info[i][2] = info[i][1];
}
}
inline void stt (int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) {
info[1][1] = a1;
info[1][2] = a2;
info[1][3] = a3;
info[2][1] = a4;
info[2][2] = a5;
info[2][3] = a6;
info[3][1] = a7;
info[3][2] = a8;
info[3][3] = a9;
}
Matrix operator * (Matrix x) {
Matrix res;
for (int k = 1; k <= 3; ++k) {
for (int j = 1; j <= 3; ++j) {
for (int i = 1; i <= 3; ++i) {
if (info[i][k] == INT_MIN || x.info[k][j] == INT_MIN) {
continue;
}
res.info [i][j] = max (res.info[i][j], info[i][k] + x.info[k][j]);
}
}
}
return res;
}
inline void print () {
for (int i = 1; i <= 3; ++i) {
for (int j = 1; j <= 3; ++j) {
printf ("%d ", info[i][j]);
}
printf ("\n");
}
}
}st;
int v[N], n, m;
struct Tree {
Matrix sum[N << 1];
int lson[N << 1], rson[N << 1], l[N << 1], r[N << 1], tot;
inline void push_up (int u) {
sum[u] = sum[rson[u]] * sum[lson[u]];
}
inline void build (int fr, int to) {
tot++;
int now = tot;
l[now] = fr;
r[now] = to;
if (fr == to) {
sum[now].stt (0, v[fr], v[fr], INT_MIN, v[fr], v[fr], INT_MIN, INT_MIN, 0);
return ;
}
int mid = (fr + to) / 2;
lson[now] = tot + 1;
build (fr, mid);
rson[now] = tot + 1;
build (mid + 1, to);
push_up (now);
}
inline void update (int fr, int p) {
if (l[p] == r[p]) {
sum[p].stt (0, v[fr], v[fr], INT_MIN, v[fr], v[fr], INT_MIN, INT_MIN, 0);
return ;
}
int mid = (l[p] + r[p]) / 2;
if (fr <= mid) {
update (fr, lson[p]);
}
else {
update (fr, rson[p]);
}
push_up (p);
}
Matrix query (int fr, int to, int p) {
if (fr <= l[p] && to >= r[p]) {
return sum[p];
}
int mid = (l[p] + r[p]) / 2;
if (to <= mid) {
return query (fr, to, lson[p]);
}
else if (fr > mid) {
return query (fr, to, rson[p]);
}
else {
return query (fr, to, rson[p]) * query (fr, to, lson[p]);
}
}
}tree;
inline void solve (int x, int y) {
if (x == y) {
printf ("%d\n", v[x]);
return ;
}
else {
Matrix st;
st.init (v[x], v[x], 0);
Matrix tmp = tree.query (x + 1, y, 1);
Matrix ans = tmp * st;
printf ("%d\n", ans.info[1][1]);
}
}
int main () {
scanf ("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf ("%d", &v[i]);
}
tree.build (1, n);
int m;
scanf ("%d", &m);
for (int i = 1; i <= m; ++i) {
int opt, x, y;
scanf ("%d%d%d", &opt, &x, &y);
if (!opt) {
v[x] = y;
tree.update (x, 1);
}
else {
solve (x, y);
}
}
return 0;
}
Conclusion
具体来讲一下我觉得坑的地方。
如果需要进行一次转移,那么
假设\(M_a\)表示位置在\(a\)的\(DP\)矩阵,\(st\)表示转移矩阵。
那么计算\(st\)从\(i-1\)转移到\(i+1\)顺序
1、\(st=M_i*st\)
2、\(st=M_{i+1}*st\)
也就是
\(st=M_{i+1}*(M_i*st)\)
于是我就直接线段树维护区间了。
但是实际上展开之后是\(st=M_{i+1}*M_i*st\)
考虑到矩阵乘法没有交换律,我们需要在\(push\_up\)和\(query\)里面交换左右儿子的顺序。
也就是倒着乘。
总之,注意要把式子看清楚再打代码。。。