Luogu P6649 「SWTR-05」Grid
阿新 • • 發佈:2020-10-12
\(\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; }