LuoGu:SP1716 GSS3

Pre

太棒了,今天早上的努力总算没有白费。

顺便说一句,本文内容简单,只是动态(DP)的基本原理(而且还不全),神犇请避让,否则会浪费时间。

原来矩阵乘法还有这么奇葩的坑(还是我太大意了),我是小看它了(话说之前有一次考试我也是用的线段树维护矩阵乘法,貌似当时代码与自己的本意不符,但是恰好是这个错误让程序违背了我的本意,最后(A)了,我真幸运!!!^_^)

Solution

动态(DP)的基本原理我就不推导了,主要提醒一下坑,在(Conclusion)里面,这里说一下我构造的矩阵。

[left[ egin{matrix} 0 & v_i & v_i\ -infty & v_i & v_i\ -infty & -infty & 0 end{matrix} ight] ]

用来计算答案的矩阵是

[left[ egin{matrix} f_{i-1}\ g_{i-1}\ 0 end{matrix} ight] ]

其中的(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 ("
");
		}
	}
}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
", 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
", 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)里面交换左右儿子的顺序。

也就是倒着乘。


总之,注意要把式子看清楚再打代码。。。

原文地址:https://www.cnblogs.com/ChiTongZ/p/11180422.html