【洛谷】 P1006 [NOIP2008提高组]传纸条

题目描述

小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排坐成一个 m 行 n 列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标 (1,1),小轩坐在矩阵的右下角,坐标 (m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。

在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。

还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用 0 表示),可以用一个[0,100] 内的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。现在,请你帮助小渊和小轩找到这样的两条路径。

输入格式

第一行有两个用空格隔开的整数 m 和 n,表示班里有 m 行 n 列。

接下来的 m 行是一个 m×n 的矩阵,矩阵中第 i 行 j 列的整数表示坐在第 i 行 j 列的学生的好心程度。每行的 n 个整数之间用空格隔开。

输出格式

输出文件共一行一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。

输入输出样例

输入 #1

3 3
0 3 9
2 8 5
5 7 0

输出 #1

34

说明/提示

【数据范围】

对于 30% 的数据,满足1≤m,n≤10。
对于 100% 的数据,满足 1≤m,n≤50。

算法知识——动态规划(DP)

一.动态规划算法基本思想:

如果各个子问题不是独立的(即重复的),且不同子问题的个数是有限的,那么如果我们可以保存已经解决的子问题的答案,而在需要时再找出已经求得的答案,这样就可以避免大量的重复计算,以此减少时间复杂度。

二.可以使用动态规划求解的问题需要满足以下基本特征:

1.最优子结构:即大问题的最优解包含子问题的最优解。

2.重叠子问题:即子问题的解会被多次用到。

3.无后效性:即求解大问题时不会改变子问题的最优解。

4.复杂问题线性化

三.实现思路:

1.定义数组,并明确其含义。

2.找出子问题和大问题之间的递推关系式(状态转移方程),并且用编程语言表达。

3.找出初始值,即无法使用第二步中关系式求解得出的值,在程序中需要手动赋值。

4.找出边界情况,在程序中需要特殊讨论。

题目思路

本题使用动态规划求解。题干中要求传两次纸条,即需要dp两次。由小轩传回给小渊的纸条路径和小渊再给小轩传一张纸条是一样的,因此程序中可以让小渊同时给小渊传两张纸条。

1.定义四维数组dp[m1][n1][m2][n2],用来存储两张纸条坐标分别为(m1,n1)与(m2,n2)时的最大好感度。同时定义二维数组clroom[ ][ ]存储每位同学的好感度。

2.dp[m1][n1][m2][n2]处的最大好感值取决于四个值中的最大值,分别为两张纸条各自从左、上两个方向传入的好感值。并且还需要加上坐标分别为(m1,n1)与(m2,n2)处同学的好感值。

dp[m1][n1][m2][n2] = Max(dp[m1][n1-1][m2-1][n2] , dp[m1-1][n1][m2][n2-1], \
					     dp[m1][n1-1][m2][n2-1] , dp[m1-1][n1][m2-1][n2]) \
	                     + room[m1][n1] + room[m2][n2];

3.无。

4.坐标边界问题可以通过扩大数组范围使得其满足递推公式。 

应当注意两张纸条需要同时传,而不是先传一条再传一条。

AC代码

法一:

#include<stdio.h>
#include<algorithm>
using namespace std;

int room[54][54];
int dp[54][54][54][54];

//定义一个函数用来比较四个值的最大值
int Max(int a,int b,int c,int d){
    int re=max(max(a,b),max(c,d));
    return re;
}

int main(){
    int m,n;
    scanf("%d%d",&m,&n);
    for(int m0 = 1; m0 <= m; m0++){
    	for(int n0 = 1; n0 <= n; n0++){
    		scanf("%d",&room[m0][n0]);
		}
	}
	//dp求解
    for(int m1 = 1; m1 <= m; m1++){
    	for(int n1 = 1; n1 <= n; n1++){
        	for(int m2 = 1; m2 <= m; m2++){
            	for(int n2 = 1; n2 <= n; n2++){
                    //处理一个同学不能传两次的问题
                    if((m1 == m2 && n1 == n2)){
                    	continue;
					}
                    dp[m1][n1][m2][n2] = Max(dp[m1][n1-1][m2-1][n2] , dp[m1-1][n1][m2][n2-1], \
					                         dp[m1][n1-1][m2][n2-1] , dp[m1-1][n1][m2-1][n2]) \
											 + room[m1][n1] + room[m2][n2];
                }
			}
		}
	}
 
    printf("%d\n",dp[m][n-1][m-1][n]);
    return 0;
 }

法二:

注意到每次传纸条后纸条位置都在同一条副对角线上,可利用其减少循环层数为三层。

实现时需要注意数组大小的调整以满足思路并避免数组越界。

#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;

int room[100][100];
int dp[110][54][54];

int Max(int a,int b,int c,int d){
    int re=max(max(a,b),max(c,d));
    return re;
}

int main(){
    int m,n;
    memset(room,0,sizeof(room));
    scanf("%d%d",&m,&n);
    for(int m0 = 1; m0 <= m; m0++){
    	for(int n0 = 1; n0 <= n; n0++){
    		scanf("%d",&room[m0][n0]);
		}
	}
	//dp求解
    for(int k = 3;k < m+n; k++){
        for(int i = 1; i < n; i++){
      		for(int j = i+1; j <= n; j++){
                if(k-i < 1 && k-j < 1)
                    continue;
                dp[k][i][j] = Max(dp[k-1][i][j] , dp[k-1][i-1][j] , dp[k-1][i][j-1], \
				                  dp[k-1][i-1][j-1]) + room[k-i][i] + room[k-j][j];
            }
        }
    }
    printf("%d",dp[m+n-1][n-1][n]);
    return 0;
 }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值