洛谷P1004 [NOIP2000 提高组] 方格取数 四维动态规划

保持沉默,是你的自由。

题目链接
题目描述
设有 N×N 的方格图 (N<=9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 0。如下图所示(见样例):
在这里插入图片描述
某人从图的左上角的 A点出发,可以向下行走,也可以向右走,直到到达右下角的 B点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 0)。
此人从 A 点到 B点共走两次,试找出 2 条这样的路径,使得取得的数之和为最大。

输入格式
输入的第一行为一个整数 N(表示 N*N的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的 0表示输入结束。

输出格式
只需输出一个整数,表示 2条路径上取得的最大的和。

输入输出样例
输入
8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0

输出
67

一开始的思路:
每走到一个位置,有两种选择,向下或向右,那怎么选择向下或向右呢?我觉得应该比较这个位置的右边和还有左边和,哪个大就往哪边走,然后把走过的位置变成0,再用这种算法走一遍
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int a[10][10];
int main(){
	int n,i,j;
	cin>>n;
	for(i=1;i<=n;i++){
		for(j=1;j<=n;j++){
			a[i][j]=0;
		}
	}
	int x,y,z;
	while(1){
	  cin>>x>>y>>z;
	  a[x][y]=z;
	  if(x==0) break;
	}
	int cnt=0;
	int rx=1,ry=1;
	if(a[1][1]!=0){
		cnt+=a[1][1];
		a[1][1]=0;
	} 
	while(rx<=n&&ry<=n){
		int sumx=0,sumy=0;
		for(i=ry+1;i<=n;i++){
			sumx+=a[rx][i];
		}
		for(i=rx+1;i<=n;i++){
			sumy+=a[i][ry];
		}
		if(sumx>=sumy) ry=ry+1;
		else if(sumx<sumy) rx=rx+1;
		if(a[rx][ry]!=0) {
			cnt+=a[rx][ry];
			a[rx][ry]=0; 
		}
	}
	rx=1,ry=1;
	while(rx<=n&&ry<=n){
		int sumx=0,sumy=0;
		for(i=ry+1;i<=n;i++){
			sumx+=a[rx][i];
		}
		for(i=rx+1;i<=n;i++){
			sumy+=a[i][ry];
		}
		if(sumx>=sumy) ry=ry+1;
		else if(sumx<sumy) rx=rx+1;
		if(a[rx][ry]!=0) {
			cnt+=a[rx][ry];
			a[rx][ry]=0; 
		}
	}
	cout<<cnt<<endl;
	return 0;
}

然后只得了40分
发现这种算法是不对的,比如这个样例就过不了:
0 10 7 4 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 3 0 0 0 0 0
0 0 6 0 0 0 0 0 0
5 0 7 0 3 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
最大33
走完一遍走第二遍的时候
0 0 0 4 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 3 0 0 0 0 0
0 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
按我的算法最大是5,但其实可以走4+3
于是看一下题目标签,发现是动态规划
然后我又开始了dp
设两个dp函数,先dp一遍,记录路径(自己写出记录路径还觉得自己是个天才,我真傻比),顺着路径逆推把走过的置0,再走一遍

#include<bits/stdc++.h>
using namespace std;
int f1[10][10],f2[10][10],p[10][10];
int a[10][10];
int main(){
	int n,i,j;
	cin>>n;
	for(i=1;i<=n;i++){
		for(j=1;j<=n;j++){
			a[i][j]=0;
		}
	}

	int x,y,z;
	while(1){
	  cin>>x>>y>>z;
	  a[x][y]=z;
	  if(x==0) break;
	}
	
     for(i=1;i<=n;i++){
     	f1[1][i]=a[1][i]+f1[1][i-1];
     	f1[i][1]=a[i][1]+f1[i-1][1];
	 }
	 for(i=2;i<=n;i++){
	 	for(j=2;j<=n;j++){
	 	 if(f1[i-1][j]>=f1[i][j-1]){
	 	 	f1[i][j]=f1[i-1][j]+a[i][j];
	 	 	p[i][j]=1;
		  }
	 	 else{
	 	 	f1[i][j]=f1[i][j-1]+a[i][j];
	 	 	p[i][j]=0;
		  }
		 }
	 }
	 if(a[n][n]!=0) a[n][n]=0;
	 int rx=n,ry=n;
	 while(rx>=1&&ry>=1){
	 	if(p[rx][ry]==1){
	 		a[rx-1][ry]=0;
	 		rx=rx-1;
		 }
		 else if(p[rx][ry]==0){
		 	a[rx][ry-1]=0;
		 	ry=ry-1;
		 }
		 if(rx==1&&ry==1) break;
	 }

	  for(i=1;i<=n;i++){
     	f2[1][i]=a[1][i]+f2[1][i-1];
     	f2[i][1]=a[i][1]+f2[i-1][1];
	 }
	 for(i=2;i<=n;i++){
	 	for(j=2;j<=n;j++){
	 	 if(f2[i-1][j]>=f2[i][j-1]){
	 	 	f2[i][j]=f2[i-1][j]+a[i][j];
	 	 	p[i][j]=1;
		  }
	 	 else{
	 	 	f2[i][j]=f2[i][j-1]+a[i][j];
	 	 	p[i][j]=0;
		  }
		 }
	 }
	 cout<<f1[n][n]+f2[n][n]<<endl;
	return 0;
}

这个想法,非常的好,然后得了80分。。
这个样例就过不了
0 0 2 3 0 0 0
0 0 3 0 0 0 0
0 0 3 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 4 0 0
0 0 0 0 4 0 0
0 0 2 0 4 0 0
最大20
dp走完一遍
0 0 0 3 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 2 0 0 0 0
最大3
加起来23,正确答案25
哦,我想了下,如果第一遍不取最大,第二遍再走,其实是能取到所有数的,所以还是不对
最后我就去看题解了,发现世界上居然有四维dp这种东西,震惊
所以这道题的正解应该是四维dp+特判,哎。真的无语了。

#include<bits/stdc++.h>
using namespace std;
int f[10][10][10][10];
int a[10][10];
int main(){
	int n,i,j,k,l;
	cin>>n;
	for(i=1;i<=n;i++){
		for(j=1;j<=n;j++){
			a[i][j]=0;
		}
	}
	int x,y,z;
	while(1){
	  cin>>x>>y>>z;
	  a[x][y]=z;
	  if(x==0) break;
	}
    for(i=1;i<=n;i++){
    	for(j=1;j<=n;j++){
    		for(k=1;k<=n;k++){
    			for(l=1;l<=n;l++){
    				f[i][j][k][l]=max(f[i-1][j][k-1][l],max(f[i-1][j][k][l-1],max(f[i][j-1][k-1][l],f[i][j-1][k][l-1])))+a[i][j]+a[k][l];
    				if(i==k&&j==l) f[i][j][k][l]-=a[i][j];//如果某个位置两条路径重合了,只能加一个的数
				}
			}
		}
	}
    cout<<f[n][n][n][n]<<endl;

	return 0;
}

又要感慨吾生也有涯知也无涯了,再多做点题吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值