do_while_true

一言(ヒトコト)

Luogu P6649 「SWTR-05」Grid

\(\text{题目链接}\)

\(Solution\)

提供一个 \(\mathcal{O}(nm\log m)\) 的做法。

题目转换一下,可以理解为从 \((1,1)\) 走到 \((n,m)\) ,每次走到一行的时候可以在前面多取一段连着的和的最小值。

把原矩阵全部取反就是最大值。

这样子转化问题就很明显了,就是一个经典问题:

求一个矩阵,只能向右向下走,求第一行走到最后一行的最大值,也就是设 \(f[i][j]\) 为走到 \((i,j)\) 的最大值。

\(f[i][j]=\max\{f[i][j-1]+a[i][j],f[i-1][j]+a[i][j]\}\) 加上了一个可以在前面多选一个以 \(j\) 为结尾的区间和的问题。

\(f[i][j-1]\) 转移过来就不需要再找前面一个在第 \(i\) 行以 \(j-1\) 为结尾的最大区间和了,因为 \(f[i][j-1]\) 已经计算了以 \(j-1\) 为结尾的最大区间和。

而对于从 \((i-1,j)\) 转移而来,需要求一个在第 \(i\) 行以 \(j\) 为结尾的最大区间和,可以利用前缀和,把前 \(j-1\) 所有前缀和放进小根堆里面,取堆顶即可。因为区间和 \(sum[j]-sum[k-1]\) 最大,需要 \(sum[k-1]\) 最小(\(sum[j]\) 代表第 \(i\) 行以 \(j\) 为结尾的前缀和,已经确定了)。

对以上两种情况分类取最大值即可。

PS: 可能人傻描述麻烦,可以理解为那个经典问题带一个可以加上一个区间和的转移,求这个值用堆贪心即可。

因为有个堆,时间复杂度 \(\mathcal{O}(nm\log m)\)(貌似被其他更优做法吊打了)

\(Code\)

#include<iostream>
#include<cstdio>
#include<queue>
#define ll long long
const int N = 1010;
inline ll Max(ll x, ll y) { return x > y ? x : y; }
inline ll Min(ll x, ll y) { return x < y ? x : y; }
inline ll read() {
	ll r = 0; bool w = 0; char ch = getchar();
	while(ch < '0' || ch > '9') {
		if(ch == '-') w = 1;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9') {
		r = (r << 3) + (r << 1) + (ch ^ 48);
		ch = getchar();
	}
	return w ? ~r + 1 : r;
}
int n, m;
ll a[N][N], f[N][N], sum, ans = -0x7fffffffffffffff;
std::priority_queue<ll>q;
int main() {
	n = read(); m = read();
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j)
			a[i][j] = -read();
	for(int i = 1; i <= m; ++i) f[1][i] = Max(f[1][i - 1] + a[1][i], a[1][i]);
	for(int i = 2; i <= n; ++i) {
		while(!q.empty()) q.pop();
		q.push(- (sum = a[i][1]));
		q.push(0);
		f[i][1] = f[i - 1][1] + a[i][1];
		for(int j = 2; j <= m; ++j) {
			sum += a[i][j];
			f[i][j] = Max(f[i][j - 1] + a[i][j], f[i - 1][j] + sum + q.top());
			q.push(-sum);
		}
	}
	for(int i = 1; i <= m; ++i) ans = Max(ans, f[n][i]);
	printf("%lld\n", -ans);
	return 0;
}
posted @ 2020-10-12 18:25  do_while_true  阅读(112)  评论(0编辑  收藏  举报