codeforces1304F2 dp + 单调队列优化

题目传送门

题意:

给你 n*m 的矩阵 a 。第 \dpi{150}i 行你可以选择以 (i,j) 为左上角,(min(n , i + 1),j+k-1)为右下角的子矩阵。

每行可以选择一次。

这 n 个子矩阵的并集的和就是答案。输出最大的答案。

数据范围: \dpi{150} 1\leqslant n \leqslant 50 , 1 \leqslant m \leqslant 2 \cdot 10^4 , 1 \leqslant k \leqslant m 。

题解:

与F1不同的是k的范围增大了,这样相交的部分就不能暴力计算了,会T到飞起。

接着这篇博客分析如何优化dp。

整一个前缀和 c[i][j] = \sum _{p=1}^{j} a[i][p] ,方便优化。

对于不相交的子矩阵,转移方法和F1没有区别,可以 O(1) 转移。

对于相交的子矩阵,考虑用单调队列,每行从左往右扫一遍,再从右往左扫一遍。

从左往右扫的转移方程:

\begin{aligned} dp[i][j] &= max(dp[i-1][p] - (c[i][p+k-1]-c[i][j-1]) + sum[i][j])\\ &= max(dp[i-1][p] - c[i][p+k-1])+c[i][j-1] + sum[i][j] \end{aligned}

然后会发现等号等边的 c[i][j-1] + sum[i][j] 只和 i,j 有关,可以认为是个常数。

然后我们需要找到的是 max(dp[i-1][p] - c[i][p+k-1])

p 的范围是 max(1,i-k+1) \leqslant p \leqslant j 。

发现这是单调队列的模型,然后就可以单调队列优化了。

从右往左扫同理。

从右往左扫的转移方程:

\begin{aligned} dp[i][j] &= max(dp[i-1][p] - (c[i][j+k-1]-c[i][p-1]) + sum[i][j])\\ &= max(dp[i-1][p] + c[i][p-1])-c[i][j+k-1] + sum[i][j] \end{aligned}

然后会发现等号等边的 -c[i][j+k-1] + sum[i][j] 只和 i,j 有关,可以认为是个常数。

然后我们需要找到的是 max(dp[i-1][p] - c[i][p+k-1]) 。

p 的范围是 j \leqslant p \leqslant min(n,j+k-1) 。

时间复杂度: O(n*m)

感受:

赛后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 ;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值