1. 程式人生 > 實用技巧 >Luogu P6649 「SWTR-05」Grid

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)\)(貌似被其他更優做法吊打了)

#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;
}