洛谷 P1006 传纸条 题解

CSDN同步

原题链接

本题 \(n \leq 100\) ,我们先分析一下 大力搜索 的效率。

众所周知, 从 \(1,1\) 走到 \(n,m\)每次只能向右走或向下走) 的方案数为:

\[C_{n+m}^m \]

简单解释:共走 \(n+m\) 步,其中 \(n\) 步向下, \(m\) 步向右。而:

\[C_{n+m}^m = C_{n+m}^n \]

组合即可。

那么,搜索的时间复杂度 至少是 \(O(C_{2 \times n}^n)\).

大概算一下:

\[C_{200}^{100}=\frac{200!}{100! \times 100!} \]

这个数很大,是我们无法接受的。

所以, 下面考虑\(dp\).

\(dp_{i,j,k,l}\) 表示 从左上角到右下角经过\(i,j\),再从右下角到左上角经过\(k,l\)的最大路径。

首先有一个结论: \(dp_{i,j,k,l}=dp_{k,l,i,j}\)

这是显然的,因为交换两个点值并不会影响答案。

那么,显然对于 \(i,j\) ,从 \(i,j-1\)\(i-1,j\) 考虑转移。

对于 \(k,l\) ,从 \(k+1,l\)\(k,l+1\) 考虑转移。

不难得出:

\[dp_{i,j,k,l}=\max(dp_{i,j-1,k-1,l},dp_{i-1,j,k-1,l},dp_{i-1,j,k,l-1},dp_{i,j-1,k,l-1})+a_{i,j}+a_{k,l} \]

也就是从相邻的四个格子加上当前两个格子的值。

而我们之前的结论是 \(dp_{i,j,k,l}=dp_{k,l,i,j}\)

又这条路径必然会经过 \((n-1,m)和(n,m-1)\)

所以答案为

\[f_{n,m-1,n-1,m} \]

时间复杂度为\(O(n^2 \times m^2)\),空间复杂度为\(O(n^2 \times m^2)\),可以通过本题。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[51][51],dp[51][51][51][51];
int max_four(int a,int b,int c,int d){
	return max(max(max(a,b),c),d);
} //求四个数中的最大值
int main()
{
  	cin>>n>>m;
  	for(int i=1;i<=n;i++)
  	for(int j=1;j<=m;j++) cin>>a[i][j];
  	for(int i=1;i<=n;i++)
  	for(int j=1;j<=m;j++)
  	for(int k=1;k<=n;k++)
  	for(int l=j+1;l<=m;l++)
		dp[i][j][k][l]=max_four(dp[i][j-1][k-1][l],dp[i-1][j][k-1][l],dp[i-1][j][k][l-1],dp[i][j-1][k][l-1])+a[i][j]+a[k][l];
	cout<<dp[n][m-1][n-1][m];
  	return 0;
}

下面考虑一个加强:

如果 \(n,m \leq 100\) 呢?

这时上述算法的空间与时间都很不理想,有超时和爆空间的可能。

对于上述的 \(dp_{i,j,k,l}\) ,你会发现:

\(i+l=j+k\)

这就是 走的步数。

那么,我们只需让 \(step=i+l=j+k\)

然后用 \(dp_{step,i,j}\) 进行转移即可。把上面的状态转移方程中的 \(k\) 改为 \(step-j\)\(l\) 改为 \(step-i\). 所以:

\[dp_{step,i,j}=\max(dp_{step-1,i,j},dp_{step-1,i-1,j-1},dp_{step-1,i,j-1},dp_{step-1,i-1,j})+a_{i,step-i+1}+a_{j,step-j+1} \]

时间复杂度为 \(O(nm \times (n+m))\),空间复杂度为 \(O(nm \times (n+m))\).

#include<bits/stdc++.h>
using namespace std;
int n,m;
int dp[101][51][51],a[51][51];
//因为step最大会达到2*n-1,所以数组要开大
int max_four(int a,int b,int c,int d){
	return max(max(max(a,b),c),d);
} //求四个数中的最大值
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++) cin>>a[i][j];
	for(int step=1;step<=n+m-1;step++)
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++){
		if(step-i+1<1 || step-j+1<1) continue; //去掉k和l是负数的情况
		dp[step][i][j]=max_four(dp[step-1][i][j],dp[step-1][i-1][j-1],dp[step-1][i][j-1],dp[step-1][i-1][j])+a[i][step-i+1]+a[j][step-j+1];
		if(i==j) dp[step][i][j]-=a[i][step-i+1]; //如果i=j,说明两点重合,需要减掉一个
	}
	cout<<dp[n+m-1][n][n];
	return 0;
}



posted @ 2020-03-15 17:50  bifanwen  阅读(188)  评论(0)    收藏  举报