dp--四维模板&空间压缩&洛谷P1004,P1006题解

1.0题目

1.1 P1004

https://www.luogu.com.cn/problem/P1004

1.2 P1006

https://www.luogu.com.cn/problem/P1006

2.0讲解

2.1 P1004

题目很明显,就是从A到B点走两次,问路径最大值。
这个题允许交叉路径。
第一个错误思路是从左上走到右下,两次DP取最大值。
但是这两次路径的选择互相影响,但我们设计的状态并没有估计到,所以显然是错误的,例如下面这个例子:

0 0 1 0
0 0 1 0
1 0 1 1
1 0 2 0

所以,我们不妨设是有两个人同时从左上角开始走,走到右下角,这样可以满足这两条路径互相影响,第一个人走的路径考虑到了第二个人。
很明显可以设思维状态表示两个人的坐标,但这样太浪费空间,
所以可以设三维状态 (F_{i,j,k}) 表示第一个人走到i行,第二个人走到j行,一共走了k步时的最优解。其中i,j均小于(min(k,n))
转移方程很明显是由上一步得到,那么是从上面走下来,还是从左面走过来,算上两个人,一共是有4种情况。
转移方程为(F_{i,j,k}=max(F_{i,j,k-1},F_{i-1,j,k-1},F_{i-1,j-1,k-1},F_{i,j-1,k-1}))
进行dp,如果初始状态算一步的话,那么结果应该是(F_{n,n,2*n-1})
代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ull unsigned long long
#define N 10
#define M number
using namespace std;

int f[N][N][N*2],n;
int a[N][N],x,y,val;

inline int Max(int a,int b){
	return a>b?a:b;
}

int main(){
	scanf("%d",&n);
	scanf("%d%d%d",&x,&y,&val);
	while(x!=0){
		a[x][y]=val;
		scanf("%d%d%d",&x,&y,&val);
	}
	for(int i=1;i<=2*n-1;i++){
		for(int j=1;j<=i&&j<=n;j++){
			for(int k=1;k<=i&&k<=n;k++){
				int jy=i-j+1,ky=i-k+1;
				f[j][k][i]+=Max(f[j][k][i-1],Max(Max(f[j][k-1][i-1],f[j-1][k][i-1]),f[j-1][k-1][i-1]))+a[j][jy]+a[k][ky];
				if(j==k) f[j][k][i]-=a[j][jy];
			}
		}
	}
	printf("%d",f[n][n][2*n-1]);
	return 0;
}

但其实空间上可以再次优化,我们发现所有的(F_{i,j,k})都是由(F_{q,p,k-1})转移得到,所以可以用类似01背包的方式把空间压缩到两维,同时需要改变循环顺序。
代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ull unsigned long long
#define N 10
#define M number
using namespace std;

int f[N][N],n;
int a[N][N],x,y,val;

inline int Max(int a,int b){
	return a>b?a:b;
}

inline int Min(int a,int b){
	return a<b?a:b;
}

int main(){
	scanf("%d",&n);
	scanf("%d%d%d",&x,&y,&val);
	while(x!=0){
		a[x][y]=val;
		scanf("%d%d%d",&x,&y,&val);
	}
	for(int i=1;i<=2*n-1;i++){
		for(int j=Min(i,n);j>=1;j--){
			for(int k=Min(i,n);k>=1;k--){
				int jy=i-j+1,ky=i-k+1;
				f[j][k]=Max(f[j][k],Max(Max(f[j][k-1],f[j-1][k]),f[j-1][k-1]))+a[j][jy]+a[k][ky];
				if(j==k) f[j][k]-=a[j][jy];
			}
		}
	}
	printf("%d",f[n][n]);
	return 0;
}

2.2 P1996

这个题和上一个题基本类似,但是唯一不同的是不允许路径交叉。
这里配合代码讲解
代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ull unsigned long long
#define N 51
#define M number
using namespace std;

int m,n;
int a[N][N],f[N][N][N*2];

inline int Max(int a,int b){
	return a>b?a:b;
}

inline int Min(int a,int b){
	return a>b?b:a;
}

int main(){
	scanf("%d%d",&m,&n);
	for(int i=1;i<=m;i++)
	    for(int j=1;j<=n;j++)
	        scanf("%d",&a[i][j]);
	
	for(int i=1;i<=n+m-1;i++){
		for(int j=1;j<=m&&j<=i;j++){
			for(int k=1;k<=m&&k<=i;k++){
				if(j==k) continue;
				int jy=i-j+1,ky=i-k+1;
				f[j][k][i]=Max(f[j][k][i-1],Max(f[j-1][k][i-1],Max(f[j][k-1][i-1],f[j-1][k-1][i-1])))+a[j][jy]+a[k][ky];
			}
		}
	}
	
	printf("%d",Max(f[m][m][m+n-1],Max(f[m-1][m][m+n-1],Max(f[m][m-1][m+n-1],f[m-1][m-1][m+n-1]))));
}

其中循环中jk是为了防止路径交叉的情况,转移方程基本类似。
还有一个不同点是输出结果时,因为当i
j时并没有更新答案,所以最右下角并没有更新,需要再加一步取最优值。

原文地址:https://www.cnblogs.com/TianMeng-hyl/p/14120889.html