题目描述
小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排坐成一个 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;
}