P7074 [CSP-J 2020] 方格取数

题目传送门

博客传送门

当时打眼一看没看见还能向上走,还在想为什么是个绿题……

进入正题。如果没有向上走的话,就是个经典简单的 dp。所以对于这个问题,我们还是先考虑 dp。

如果说我们按行从上往下转移肯定是有后效性的,不可行。但是我们发现,如果我们向右走,那我们是回不到左边的,这样就没有后效性了。所以我们从左往右转移,也就是从 \(j-1\) 列往第 \(j\) 列转移。

而对于这两列中的任意两个位置 \((k,j-1)\)\((i,j)\),假设我们在 \((k,j-1)\) 这个位置向右走,那么由于不能走重复的格子,肯定是先向右,然后一直向上或向下的,不可能出现折返的情况。

P7074_1_1

P7074_2_1

我们设 \(dp_{i,j}\) 表示考虑到第 \(j\) 列,第 \(i\) 行的最大得分。这样我们就可以枚举上一列里向右走的那个位置了。朴素的转移方程如下:

\[dp_{i,j}=\max\limits_{k=1}^{n}{dp_{k,j-1}+\sum\limits_{l=min(k,j)}^{max(k,j)}{a[i][l]}} \]

这样是 \(O(n^4)\) 的,过不去啊。但是我们可以先求每行竖直方向的前缀和,优化掉累加的一个 \(n\),这样就是 \(O(n^3)\),还是过不去。

我们先把优化后的转移式子写一下:

\[dp_{i,j}=\max\limits_{k=1}^{n}{ \begin{cases} dp_{k,j-1}+sum_{i,j}-sum_{k-1,j} (k \le i)\\ dp_{k,j-1}+sum_{k,j}-sum_{i-1,j} (k > i)\\ \end{cases} } \]

因为我们的枚举顺序肯定是 \(j\) 在外层循环(首先保证是由 \(j-1\) 列转移来),所以枚举 \(i,k\)\(j\) 是固定的,所以我们可以把有关 \(i\)\(k\) 的项分开,也就是:

\[dp_{i,j}=\max\limits_{k=1}^{n}{ \begin{cases} (dp_{k,j-1}-sum_{k-1,j})+(sum_{i,j}) (k \le i)\\ (dp_{k,j-1}+sum_{k,j})-(sum_{i-1,j}) (k > i)\\ \end{cases} } \]

对于 \(k \le i\) 的情况,我们可以正序枚举 \(i\),并时刻记录一个最大值 \(mx\),其中 \(mx=\max\limits_{k=1}^{i}{dp_{k,j-1}-sum_{k-1,j}}\)

这样我们对于一个新加入的可以拿去更新的 \(i\) 直接 \(O(1)\) 更新,然后用 \(mx\) 转移 \(dp_{i,j}\) 即可。\(k > i\) 的情况同理。(不过要倒序枚举)

由于是取最大值,所以我们正着来一遍倒着来一遍,这样对于每个 \((i,j)\),它都被向下来的最优解和向上来的最优解更新过一遍,所以本做法是对的。

代码:

P7074
#include<bits/stdc++.h>
#define int long long
using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<48){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}

inline void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x<10) putchar(x+'0');
	else write(x/10),putchar(x%10+'0');
}

const int N=1314;
const int inf=1e16;
int n,m,a[N][N],dp[N][N],sum[N][N];
//sum[i][j]:对第j列做前缀和,当前做到第i行 

signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			a[i][j]=read();
			sum[i][j]=sum[i-1][j]+a[i][j];
		}
	}
	//由于结果可能为负,所以初始化一定是赋极小值 
	for(int j=1;j<=n;j++){
		for(int i=1;i<=m;i++){
			dp[j][i]=-inf;
		}
	}
	//对于第一列的数来说,显然只能从左上角往下走 
	for(int j=1;j<=n;j++){
		dp[j][1]=sum[j][1];
	}
	for(int i=2;i<=m;i++){
		int mx=-inf;
		//mx:同题解 
		//计算k<=i时的最优解 
		for(int j=1;j<=n;j++){
			//i是当前新加入的可更新mx的位置,更新mx 
			mx=max(mx,dp[j][i-1]-sum[j-1][i]);
			dp[j][i]=max(dp[j][i],mx+sum[j][i]);
		}
		//计算k>i时的最优解 
		//同上 
		mx=-inf;
		for(int j=n;j>=1;j--){
			mx=max(mx,dp[j][i-1]+sum[j][i]);
			dp[j][i]=max(dp[j][i],mx-sum[j-1][i]);
		}
	}
	int ans=dp[n][m];
	printf("%lld",ans);
	return 0;
}
posted @ 2025-10-28 10:20  qwqSW  阅读(6)  评论(0)    收藏  举报