线性dp P1004 【方格取数】二维误区/四维正解/三维优化

代码比较简单的一题,重在思路(除非写假了)

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1004

我的最初思路是两次二维dp,即贪心的取,用pre记录前一个位置,只有80pts,要是是在蓝桥拿分就可以跑路了(bushi)

代码如下

// Problem: 
//     P1004 [NOIP2000 提高组] 方格取数
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1004
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<cstring>
using namespace std;
const int N=10;
int ans=0;
int f[N][N];
int pre[N][N];//记录前一个位置
int val[N][N];


int main(){
	int n;cin>>n;
	int a,b,c;
	while(cin>>a>>b>>c){
		if(a==0&&b==0&&c==0){
			break;
		}
		val[a][b]=c;
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			f[i][j]=max(f[i-1][j],f[i][j-1])+val[i][j];
			if(f[i-1][j]>=f[i][j-1]){
				pre[i][j]=1;//1从上一行来
			}
			else{
				pre[i][j]=2;//2从左边来
			}
		}
	}
	for(int i=1;i<=n;++i) pre[1][i]=2;
	for(int i=1;i<=n;++i) pre[i][1]=1;
	ans+=f[n][n];
	memset(f,0,sizeof f);
	int line=n;
	int rank=n;
	// for(int i=1;i<=n;++i){
		// for(int j=1;j<=n;++j){
			// cout<<val[i][j]<<' ';
		// }
		// cout<<endl;
	// }
	// cout<<endl;
	// for(int i=1;i<=n;++i){
		// for(int j=1;j<=n;++j){
			// cout<<pre[i][j]<<' ';
		// }
		// cout<<endl;
	// }
	val[rank][line]=0;
    while(rank>1||line>1){
		if(pre[rank][line]==1){
			rank-=1;
		}
        else{
        	line-=1;
        }
        val[rank][line]=0;
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			f[i][j]=max(f[i-1][j],f[i][j-1])+val[i][j];
		}
	}
	// cout<<ans<<endl;
	ans+=f[n][n];
	cout<<ans<<endl;
	
	
	return 0;
}

这样子代码的问题在于,这道题两次要同时最优,并不能直接贪心

(或许是可以做的,但是要考虑的复杂情况比较多,本蒟蒻决定跳过)

7
1 3 2
1 4 3
2 3 3
3 3 3
5 5 4
6 5 4
7 3 2
7 5 4
0 0 0

例如这组数据,用上面的代码跑一下就会发现,不使用贪心(每次都取最大)的方法能获得更多的价值,于是,我们可以考虑使用一个四维dp来同时维护两次操作

// Problem: 
//     P1004 [NOIP2000 提高组] 方格取数
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1004
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<cstring>
using namespace std;
const int N=10;
int ans=0;
int f[N][N][N][N];
int val[N][N];


int main(){
	int n;cin>>n;
	int a,b,c;
	while(cin>>a>>b>>c){
		if(a==0&&b==0&&c==0){
			break;
		}
		val[a][b]=c;
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			for(int k=1;k<=n;++k){
				for(int l=1;l<=n;++l){
					f[i][j][k][l]=max(max(f[i-1][j][k-1][l],f[i-1][j][k][l-1]),
					max(f[i][j-1][k-1][l],f[i][j-1][k][l-1]))+val[i][j]+val[k][l];
					if(i==k&&j==l) f[i][j][k][l]-=val[i][j];
				}
			}
		}
	}
	cout<<f[n][n][n][n]<<endl;
	
	
	return 0;
}

这里的四维也比较好理解,其实就是枚举了两种路线分别经过每一个点的状态,并且放在一起考虑(相等要减掉val[i][j])

另外:写二维的时候还没有明显的感觉,后面发现这不就是深搜变种嘛,洛谷有一题很像

本题数据是N<=9

但是我们有更好的三维优化   三维思路出处

思路:用k来表示x坐标和y坐标的和,然后通过y算出x坐标,创造一个f[k][y1​][y2​]来跑dp

和前面思路大致相同,有转移公式(思路,不是原代码)

f[k][i][j]=max(f[k−1][i][j],f[k−1][i−1][j],f[k−1][i][j−1],f[k−1][i−1][j−1])
+[(i==j)?map[k−i+1][i]:map[k−i+1][i]+map[k−j+1][j]]

其实这个方程不难理解,可以类似于用bfs的扩散来协助理解。枚举的k实际上就是步数,i,j就是枚举该不输的每个点。在这个dp公式中,如果i不动,k-1,那么其实就是行数-1,否则就是列数-1,巧妙的表现了两种情况。

感觉是很熟悉的优化,但是我想不到

代码如下:

// Problem: 
//     P1004 [NOIP2000 提高组] 方格取数
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1004
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<cstring>
using namespace std;
const int N=20;//数组最好开大一点,不然会越界(但是洛谷能过就是了)
int ans=0;
int f[N][N][N];
int val[N][N];

int main(){
	int n;cin>>n;
	int a,b,c;
	while(cin>>a>>b>>c){
		if(a==0&&b==0&&c==0){
			break;
		}
		val[a][b]=c;
	}
	for(int k=2;k<=n*2;++k){//从2开始,不能从3,因为要考虑(1,1)的情况
		for(int i=1;i<=min(k-1,n);++i){//k-1  是因为要考虑横坐标至少有1
			for(int j=1;j<=min(k-1,n);++j){
					f[k][i][j]=max(max(f[k-1][i-1][j-1],f[k-1][i][j-1]),
					max(f[k-1][i-1][j],f[k-1][i][j]))+val[k-j][j]+val[k-i][i];
					if(i==j) f[k][i][j]-=val[k-i][i];
                    //重复就减掉
			}
		}
	}
	cout<<f[n*2][n][n]<<endl;
	
	
	return 0;
}
  • 25
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值