2016年校赛 A题—地宫寻宝 建立三位数组的DP问题

00032:Problem A 地宫寻宝

总时间限制:
1000ms
内存限制:
65536kB
描述

Hujie在普吉岛观摩2016年ACM-ICPC World Final期间,从当地人手中得到几张藏宝图。图上画的是岛上的若干座矩形地下城堡,城堡每个房间都是正方形的且藏有宝藏。城堡南北向有N个房间,东西向有M个房间,且相邻的房间是相通的。当地人告诉他,由于是地下城堡氧气含量稀少,一直没人敢贸然进去。Hujie想到了一种方法,携带氧气瓶从西北角第一个房间(1,1)进入城堡探险,一路向东或向南走,带一些宝藏从东南角的房间(N,M)出来,但是他不知道自己的方案究竟能否可行,能带出来多少宝藏?

输入
输入包含多组数据。
每组数据第1行包含3个正整数 N,M,Y (1≤N,M≤100, 0≤Y≤1000),分别表示城堡在南北方向上的房间数N,在东西方向上的房间数M,以及携带的氧气量Y;
紧接着N行,每行M个正整数P ij(0≤P≤10000),表示坐标为(i,j)的房间中的宝藏价值;之后N行,每行M个正整数C ij (0≤C≤1000),表示经过坐标为(i,j)的房间所需要消耗的氧气量。
输入数据以N=M=Y=0结束。
输出
对应每一组数据,输出"case #: "表示第#组数据的结果,若能够走出城堡则输出能够携带的最大宝藏价值总和,若不能走出城堡则输出"No way!",每个结果占一行。
样例输入
2 2 5
1 2 
3 4
1 1
1 1
2 3 4
1 1 1
1 1 1
2 2 2
2 2 2
0 0 0
样例输出
case 1: 8
case 2: No way!

这道题目让我校赛时候十分懵逼啊,当时刚刚学的DP,不是很熟练,就没向那方向去想,只想到了BFS和DFS,但是细思极恐,这题目根本不能用搜索来做,否则情况太多,BFS在20*20的迷宫就爆了,DFS有那么多的情况要去搜索,然后每次比较下max什么的,肯定要超时。

这道题要求是无后效性,前面的情况不能影响后面的情况,因为可能前面你按照宝藏价值高低去走,消耗大量氧气,但是后面基本不消耗氧气,而且宝藏价值更加高,你却没氧气去走了。这约束了你不能用贪心来做,贪心对于acm这种题目十分多变的来说,了解了解也差不多了,基本用不上啊。

对于我一个刚学DP的低端acmer来说,要做一个图的01背包,十分的蛋疼啊,因为图要连通,有方向。最基础的01背包,只需要考虑每个物品加不加而已,这个图就要连起来,而且这道题来说,一加就要加进去了,这是很关键的地方,没有说,我判断(2,2)时候,只取(1,2)这个点的值,存在(2,2)这个点里面,就是说判断max时候,你前面这个点加上自己的价值还没我前面那个可以继承的点高,然后就可以直接选择前面那个点。所以说如果这个点加进来会爆掉氧气的话,这个点就直接舍弃了。

而且我们做得01背包已经是二维数组形式了,这个变成了图,当然要变成三维数组了

这道题是关键是怎么去舍弃那些不能用的点,就是向下或者向右到这个点,都会爆掉氧气。由于这道题是只能向下或者向右走的,所以我采用的方法很简单,先初始化((1,1)到(1,m))和((1,1)到(n,1)),后面的点全部都是由这两个地方来的,就不需要加什么if去判断新的点是不是在这个范围内了。然后就是一种感觉很有后效性的方法,如果这个点可以加入,那就是正常的值,如果不能用,那就赋值为-1(我这地方一开始赋值为0好直接在&&判断,但是这道题目的数据范围有点蛋疼。。。)

所以说了这么多,到底怎么去舍弃那些不能用的点呢?我的代码如下:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define max(a,b) a>b?a:b
int dp[105][105][1005];
bool G[105][105],flag;
int main()
{
	int n,m,y,k,x,i,j,t,ppp=1;
	int val[105][105],cost[105][105];
	while(scanf("%d%d%d",&n,&m,&y)!=EOF)
	{
		if(n + m + y == 0)
			break;
		memset(G,true,sizeof(G));
		for(i=1;i<=n;i++)
			for(j=1;j<=m;j++)
				scanf("%d",&val[i][j]);
		
		for(i=1;i<=n;i++)
			for(j=1;j<=m;j++)
				scanf("%d",&cost[i][j]);
		
		for(i=0;i<=y;i++)
			if(i>=cost[1][1])
				dp[1][1][i]=val[1][1];
			else
				dp[1][1][i] = -1;//就是这里,我一开始下面这些都是赋值为0,最后判断最后那个点是不是0,不是就输出,
                                                //但是某些数据正确但是这种写法会错误,全改成-1就正确了
		
		for(i=2;i<=m;i++)
		{
			for(j=0;j<=y;j++)
				if(j>=cost[1][i] && dp[1][i-1][j-cost[1][i]] != -1)
					dp[1][i][j] = val[1][i] + dp[1][i-1][j-cost[1][i]];
				else
					dp[1][i][j] = -1;
		}
		
		for(i=2;i<=n;i++)
		{
			for(j=0;j<=y;j++)
				if(j>=cost[i][1] && dp[i-1][1][j-cost[i][1]] != -1)
					dp[i][1][j] = val[i][1] + dp[i-1][1][j-cost[i][1]];
				else
					dp[i][1][j] = -1;
		}
		
		for(i=2;i<=n;i++)
		{
			for(j=2;j<=m;j++)
			{
				for(k=0;k<2;k++)
				{
					if(k == 0)
					{
						for(x=0;x<=y;x++)
						{
							if(x>=cost[i][j] && dp[i][j-1][x-cost[i][j]] != -1)
							{
								dp[i][j][x] = val[i][j] + dp[i][j-1][x-cost[i][j]];
							}
							else
								dp[i][j][x] = -1;
						}
					}
					else
					{
						for(x=0;x<=y;x++)
						{
							if(x>=cost[i][j])
								if(dp[i][j][x])
								{
									if(dp[i-1][j][x-cost[i][j]] != -1)
										dp[i][j][x] = max(val[i][j] + dp[i-1][j][x-cost[i][j]], dp[i][j][x]);
								}
								else if(dp[i-1][j][x-cost[i][j]] != -1)
									dp[i][j][x] = val[i][j] + dp[i-1][j][x-cost[i][j]];
								else
									dp[i][j][x] = -1;
							else
								dp[i][j][x] = -1;
						}
					}
				}
			}
		}
		if(dp[n][m][y]!=-1)
			printf("case %d: %d\n",ppp++,dp[n][m][y]);
		else
			printf("case %d: No way!\n",ppp++);
	}
	return 0;
} 

完完全全自己想出来的代码ac了,感觉就是有成就感哈哈,花费了五个小时。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值