算法设计与分析课设-探寻宝藏-双线程DP问题-C语言

目录

1.题目要求:

2.详细设计思想

3.算法优化

4.完整代码

5.输出结果


1.题目要求:

传说HMH大沙漠中有一个M*N迷宫,里面藏有许多宝物。某天,Dr.Kong找到了迷宫的地图,他发现迷宫内处处有宝物,最珍贵的宝物就藏在右下角,迷宫的进出口在左上角。当然,迷宫中的通路不是平坦的,到处都是陷阱。Dr.Kong决定让他的机器人卡多去探险。 但机器人卡多从左上角走到右下角时,只会向下走或者向右走。从右下角往回走到左上角时,只会向上走或者向左走,而且卡多不走回头路。(即:一个点最多经过一次)。当然卡多顺手也拿走沿路的每个宝物。 Dr.Kong希望他的机器人卡多尽量多地带出宝物。请你编写程序,帮助Dr.Kong计算一下,卡多最多能带出多少宝物。

输入第一行:
K 表示有多少组测试数据。
接下来对每组测试数据:
第1行: M N
第2~M+1行: Ai1 Ai2 ……AiN (i=1,……,m)
【约束条件】
2≤k≤5 1≤M, N≤50 0≤Aij≤100 (i=1,….,M; j=1,…,N)
所有数据都是整数。 数据之间有一个空格。
输出
对于每组测试数据,输出一行:机器人卡多携带出最多价值的宝物数

样例输入
2
2 3
0 10 10
10 10 80
3 3
0 3 9
2 8 5
5 7 100
样例输出
120
134

这是双进程DP问题,首先,假设出发点为A 终点为B 那么,根据题目给出的条件,可以推出A->B的动态转移方程为 dp[i][j] = max(dp[i-1][j],dp[i][j-1]) + a[i][j]; 由于,同理可得B的情况,那么,题目的意思是A->B 然后 B -> A我们可以假设同时从A点出发,得到两条不同路径,这个是一样的效果。所以,我们可以得到一个动态转移方程
dp[i][j][p][q] = max(dp[i-1][j][p-1][q],dp[i-1][j][p][q],dp[i][j-1][p-1][q],dp[i][j-1][p][q-1]) 因为 每次只能移动一步,即 i+1 或j+1 那么 i+j是移动的步数 因为从A点开始移动的,经过相同的步数,肯定能得到i+j = p+q

还有一点要注意一下,这题与NYOJ 61是同类问题,但是,有一点细节要注意,最后终点的值也要算上,上面的动态方程得到的值不包含两个A 和 B的值,因为 A是起点,所以,他的值一般是0,所以,得到最后的结果应该是 int sum = max(dp[m-1][n][m-1][n],dp[m-1][n][m][n-1],dp[m][n-1][m-1][n],dp[m][n-1][m][n-1]) + a[m][n]; 

2.详细设计思想

本项目设计是一个最值求取问题,具有最优子结构特点,如果想要走完全程宝物价值最大,那么每走一步拿到的宝物价值都得最大,所以应使用动态规划算法。

 假设出发点为A,终点为B,用(i,j)表示卡多所在的位置。A到B这个过程的状态是dp[i][j],表示的是走到(i,j)位置时可以拿到的宝物价值的最大值。状态转移变量是i,j的递增,也就是移动,这个移动方向有两种:向下和向右,所以,在状态转移过程中有两种选择,需要从这两种选择中找出最优解来做出决策。max(dp[i-1][l1],dp[i][j-1])代表做决策的过程。状态转移:(i,j)位置宝物价值最大值=(i-1, j) ,(i,j-1)两个位置宝物价值最大值中的较大一个+(i,j)处宝物价值。

 所以可以得出状态转移方程为:dp[i][j]= max(dp[i-1][j].dp[i][j-1])+ map[i][j]

B到A的过程同理,可以看做是两个人同时从A出发,走到B拿宝物。

用h1、l1表示第一个人坐标,h2、l2表示第二个人坐标,类比上面一个人的情况,可以得出两个人的状态转移方程。状态是dp[h1][l1][h2][l2],表示第一个人走到(h1,l1)位置,第二个人走到(h2,l2)位置时两人可以拿到的宝物价值之和的最大值。状态转移变量是h1、l1、h2、l2的递增,也就是两个人的移动,一个人移动方向有两种,那么两个人就有四种,全向下、全向右、一下一右、一右一下,所以,在状态转移过程中有四种选择,需要从这四种选择中找出最优解来做出决策。

所以可以得出状态转移方程:dp[h1][l1][h2][l2]=max(dp[h1-1][h1][h2-1][l2],dp[h1-1][l1][h2][l2-1],dp[h1]ll1-1][h2-1][l2],dp[h1][l1-1][h2][l2-1]) +map[h1][l1]+map[h2][l2]

时间复杂度:O(m*m*n*n)

3.算法优化

    但以上算法仍存在一些问题,例如:时间复杂度较高、程序嵌套四层循环、存在无效运算、输出结果时会因为操作用变量调用的数组元素可能越界产生安全问题的问题。针对以上几点问题,我做了如下优化:

(1)因为两个人都是从A点开始移动的,那么h1+l1、h2+l2等于移动的步数。因为两人每次只能移动一步,步数永远相等,得到h1+l1 = h2+l2=step,便可通过此等式来降维,通过移项来用h1、h2来表示l2、l1。即可将四重循环缩减为三重循环。时间复杂度由O(m*m*n*n)减少为O((n+m)*m*n) ,同时空间复杂度也同理缩小。

(2)减少无效运算,通过输出dp表时发现,存在因为组合问题带来的无效运算,原因是第一个人走第一条路,第二个人走第二条与第一个人走第二条,第二个人走第一条需要算两次,但得出结果只需要一次即可,便有了一次无效运算。

//输出dp表
for (step = 2; step <= m + n; step++)
{
	for (h1 = 1; h1 <= m; h1++) 
	{
		for (l1 = 1; l1 <= m; l1++) 
		{
			printf("%d ",dp[step][h1][l1]);
		}
		printf("\n");
	}
	printf("\n");
}

 只需将三重循环中最内层循环的初始条件h2=1改为h2=h1即可。

 

(3)结果输出方式,文件给出的输出方式形式上较为复杂,且代码在编译器上会产生绿色下划线和警告

 这是因为操作用变量调用的数组元素可能越界从而产生安全问题。

可将其改为printf("%d\n\n", dp[m + n][m][m]),不过这样就需要在三层循环的最外层循环的条件里,将step<m+n改为step<=m+n。

 

虽然会多算一个二重循环,但是更加安全,可读性更强。

4.完整代码

#include<stdio.h>
#include<string.h>
int map[51][51];
int dp[102][51][51];

int max(int a, int b)
{
	if (a > b)
		return a;
	else
		return b;
}

int main() {
	int k;
	scanf_s("%d",&k);
	while (k--) {
		int m, n;
		scanf_s("%d",&m);
		scanf_s("%d",&n);
		memset(map, 0, sizeof(map));
		memset(dp, 0, sizeof(dp));
		int i, j;
		for (i = 1; i <= m; i++)     //输入map
		{
			for (j = 1; j <= n; j++) 
			{
				scanf_s("%d", &map[i][j]);
			}
		}
		int step, h1, h2, l1, l2;
		for (step = 2; step <= m + n; step++) 
		{
			for (h1 = 1; h1 <= m; h1++) 
			{
				for (h2 = h1; h2 <= m; h2++)     //设定默认路径,h2=1与h2=h1相比,
					                             //通过输出dp表可以看出,h2=h1时间复杂度减小一半
				{
					l1 = step - h1;//step=行数+列数=h+l
					l2 = step - h2;
					if (l1 <= 0 || l2 <= 0 || l1 > n || l2 > n)//讨论越界
						continue;
					dp[step][h1][h2] = max(max(dp[step - 1][h1][h2], dp[step - 1][h1][h2 - 1]), max(dp[step - 1][h1 - 1][h2], dp[step - 1][h1 - 1][h2 - 1]))
						+ map[h1][l1] + map[h2][l2];//状态转移方程
					if (h1 == h2) //处理重合
					{
						dp[step][h1][h2] = dp[step][h1][h2] - map[h1][l1];
					}
				}
			}
		}
		printf("卡多最多能带出的宝物为:");
		printf("%d\n\n", dp[m + n][m][m]);
	}
	return 0;
}

5.输出结果

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值