题目传送门
题意:
给你 的矩阵
。第
行你可以选择以
为左上角,
为右下角的子矩阵。
每行可以选择一次。
这 个子矩阵的并集的和就是答案。输出最大的答案。
数据范围: 。
题解:
与F1不同的是k的范围增大了,这样相交的部分就不能暴力计算了,会T到飞起。
接着这篇博客分析如何优化dp。
整一个前缀和 ,方便优化。
对于不相交的子矩阵,转移方法和F1没有区别,可以 转移。
对于相交的子矩阵,考虑用单调队列,每行从左往右扫一遍,再从右往左扫一遍。
从左往右扫的转移方程:
然后会发现等号等边的 只和
有关,可以认为是个常数。
然后我们需要找到的是 。
的范围是
。
发现这是单调队列的模型,然后就可以单调队列优化了。
从右往左扫同理。
从右往左扫的转移方程:
然后会发现等号等边的 只和
有关,可以认为是个常数。
然后我们需要找到的是 。
的范围是
。
时间复杂度: 。
感受:
赛后AK div2也好舒服。
看了红名大佬用了deque,发现确实可以用单调队列优化,写成数学表达式就好分析多了。
代码:
#include<bits/stdc++.h>
using namespace std ;
typedef long long ll ;
const int maxn = 55 ;
const int maxm = 2e4 + 5 ;
int n , m , k ;
int a[maxn][maxm] ;
int dp[maxn][maxm] ;
int sum[maxn][maxm] ;
int c[maxn][maxm] ;
int q[maxm] ;
void init()
{
for(int i = 1 ; i <= n ; i ++)
for(int j = 1 ; j <= m ; j ++)
c[i][j] = c[i][j - 1] + a[i][j] ;
for(int i = 1 ; i <= n ; i ++)
{
for(int j = 1 ; j <= k ; j ++)
sum[i][1] += a[i][j] ;
for(int j = 2 ; j + k - 1 <= m ; j ++)
sum[i][j] = sum[i][j - 1] - a[i][j - 1] + a[i][j + k - 1] ;
}
for(int i = 1 ; i <= n ; i ++)
for(int j = 1 ; j + k - 1 <= m ; j ++)
sum[i][j] += sum[i + 1][j] ;
}
void solve()
{
for(int j = 1 ; j + k - 1 <= m ; j ++) dp[1][j] = sum[1][j] ;
for(int i = 2 ; i <= n ; i ++)
{
int pre = 0 ;
int l = 1 , r = 0 ;
for(int j = 1 ; j + k - 1 <= m ; j ++)
{
if(j - k >= 1) pre = max(pre , dp[i - 1][j - k]) ;
if(j - k >= 1) dp[i][j] = max(dp[i][j] , pre + sum[i][j]) ;
while(l <= r && q[l] < j - k + 1) l ++ ;
while(l <= r && dp[i - 1][q[r]] - c[i][q[r] + k - 1]
< dp[i - 1][j] - c[i][j + k - 1]) r -- ;
q[++ r] = j ;
int cost = dp[i - 1][q[l]] - c[i][q[l] + k - 1] ;
int x = c[i][j - 1] + sum[i][j] ;
dp[i][j] = max(dp[i][j] , cost + x) ;
}
int suf = 0 ;
l = 1 , r = 0 ;
for(int j = m - k + 1 ; j >= 1 ; j --)
{
if(j + 2 * k - 1 <= m) suf = max(suf , dp[i - 1][j + k]) ;
if(j + 2 * k - 1 <= m) dp[i][j] = max(dp[i][j] , suf + sum[i][j]) ;
while(l <= r && q[l] > j + k - 1) l ++ ;
while(l <= r && dp[i - 1][q[r]] + c[i][q[r] - 1]
< dp[i - 1][j] + c[i][j - 1]) r -- ;
q[++ r] = j ;
int cost = dp[i - 1][q[l]] + c[i][q[l] - 1] ;
int x = -c[i][j + k - 1] + sum[i][j] ;
dp[i][j] = max(dp[i][j] , cost + x) ;
}
}
int ans = 0 ;
for(int j = 1 ; j + k - 1 <= m ; j ++) ans = max(ans , dp[n][j]) ;
printf("%d\n" , ans) ;
}
int main()
{
scanf("%d%d%d" , &n , &m , &k) ;
for(int i = 1 ; i <= n ; i ++)
for(int j = 1 ; j <= m ; j ++)
scanf("%d" , &a[i][j]) ;
init() ;
solve() ;
return 0 ;
}