【洛谷】P1006 传纸条

题目链接(小教室,大教室的题目在最后)

题目描述

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

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

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

输入输出格式

输入格式:

输入文件,第一行有 2 个用空格隔开的整数 m 和 n ,表示班里有 m 行 n 列。

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

输出格式:

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

输入输出样例

输入样例#1: 复制

3 3
0 3 9
2 8 5
5 7 0

输出样例#1: 复制

34

可以将问题等价于两张纸条同时从左上角传出,只能向右或向下传,由于每个同学只会帮传一次,所以这两张纸条的路线不能相交。

https://blog.csdn.net/qq_40889820/article/details/81432672可知,可以用dp[i][j][k][l]来表示第一张纸条从map[1][1]传到map[i][j]和第二张纸条从map[1][1]传到map[k][l]的好心程度之和的最大值。显然最后只能一张纸条由map[m][n-1]传到map[m][n],另一张由map[m-1][n]传到map[m][n]。也可知一开始一张纸条向下传,另一张向右传。我们假设第一张纸条是向下传的,第二张纸条是向右传的,为了避免两条路线相交,在循环的时候控制一下就好了,要么从纵坐标入手:l>j,要么从横坐标入手:k<i (原因后面解释)。

那么最终答案就是dp[m][n-1][m-1][n],

状态转移方程是dp[i][j][k][l]=max(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1],dp[i][j-1][k-1][l],dp[i][j-1][k][l-1])+map[i][j]+map[k][l]。

那么可以这样

for(int i=2;i<=m;i++)
	{
		for(int j=1;j<=n-1;j++)
		{
			for(int k=1;k<i;k++)//注意这里
			{
				for(int l=2;l<=n;l++)
				dp[i][j][k][l]=max(
				max(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1]),
				max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1]))
				+map[i][j]+map[k][l];
			}
		}
	}

也可以是这样

for(int i=2;i<=m;i++)
	{
		for(int j=1;j<=n-1;j++)
		{
			for(int k=1;k<=m-1;k++)
			{
				for(int l=j+1;l<=n;l++)//注意这里
				dp[i][j][k][l]=max(
				max(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1]),
				max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1]))
				+map[i][j]+map[k][l];
			}
		}
	}

四维 dpAC代码:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<iomanip>
 5 #include<vector>
 6 #include<cmath>
 7 #include<stack>
 8 using namespace std;
 9 int dp[51][51][51][51];
10 int map[51][51];//好心程度
11 int main()
12 {
13     int m,n;
14     cin>>m>>n;
15     for(int i=1;i<=m;i++)
16     for(int j=1;j<=n;j++)
17     cin>>map[i][j];
18     
19     memset(dp,0,sizeof(dp));
20     for(int i=2;i<=m;i++)
21     {
22         for(int j=1;j<=n-1;j++)
23         {
24             for(int k=1;k<=m-1;k++)
25             {
26                 for(int l=j+1;l<=n;l++)
27                 dp[i][j][k][l]=max(
28                 max(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1]),
29                 max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1]))
30                 +map[i][j]+map[k][l];
31             }
32         }
33     }
34     cout<<dp[m][n-1][m-1][n];
35 }
View Code

现在我们来考虑优化,分析一下纸条传递的规律,发现他们向下和向右走的总步数是相等的(这下上面留下的问题就解决啦!总步数相同的前提下,同列必定同行,同行必定同列,即重合),map[1][1]传到map[i][j]的总步数是i-1+j-1=i+j-2,即第一张纸条从map[1][1]传到map[m][n-1]总的步数是m-1+n-1-1=m+n-3,同理,第二张纸条总的步数也是如此,那么只要知道走的总步数和向下(右)走的步数,向右(下)的步数便可通过计算得出。

用三维dp[s][i][j]表示走的总步数为s,第一张纸条向下走了i步由map[1][1]传到了map[i+1][s+1-i],第二张纸条向下走了j步由map[1][1]传到了map[j+1][s+1-j]。

那么最终答案等于dp[m+n-3][m-1][m-2]。

状态转移方程是dp[s][i][j]=max(dp[s-1][i-1][j],dp[s-1][i][j-1],dp[s-1][i][j],dp[s-1][i-1][j-1])+map[i+1][s+1-i]+map[j+1][s+1-j]

三维dp AC代码:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<string>
 4 #include<iomanip>
 5 #include<vector>
 6 #include<cmath>
 7 #include<stack>
 8 using namespace std;
 9 int dp[102][51][51];
10 int map[51][51];
11 int main()
12 {
13     int m,n;
14     cin>>m>>n;
15     for(int i=1;i<=m;i++)
16     for(int j=1;j<=n;j++)
17     cin>>map[i][j];
18     
19     for(int s=1;s<=m+n-3;s++)
20     {
21         for(int i=1;i<=m-1;i++)
22         {
23                     //j=0的时候下标出现了负数,经实验,默认为0,不过查了一下貌似挺危险,不管,过了就是过了 
24             for(int j=0;j<i;j++)
25             dp[s][i][j]=max(
26             max(dp[s-1][i][j],dp[s-1][i-1][j-1]),
27             max(dp[s-1][i][j-1],dp[s-1][i-1][j]))
28             +map[i+1][s+1-i]+map[j+1][s+1-j];
29         }
30     }
31     cout<<dp[m+n-3][m-1][m-2];
32 }
View Code

还可不可以再优化呢?当然!用滚动数组试一下( ´-ω ・)▄︻┻┳══━一

不过要注意的是i和j的枚举要倒叙,防止状态在转移之前被修改

二维dp AC代码:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<string>
 4 #include<iomanip>
 5 #include<vector>
 6 #include<cmath>
 7 #include<stack>
 8 using namespace std;
 9 int dp[210][210];
10 int map[210][210];
11 int main()
12 {
13     int m,n;
14     cin>>m>>n;
15     for(int i=1;i<=m;i++)
16     for(int j=1;j<=n;j++)
17     cin>>map[i][j];
18     
19     for(int s=1;s<=m+n-3;s++)
20     {
21         for(int i=min(m-1,s);i>=1;i--)//最开始的步数要判断一下
22         {
23             for(int j=i-1;j>=0;j--)//和i都要倒叙枚举
24             dp[i][j]=max(
25             max(dp[i][j],dp[i-1][j-1]),
26             max(dp[i][j-1],dp[i-1][j]))
27             +map[i+1][s+1-i]+map[j+1][s+1-j];
28         }
29     }
30     cout<<dp[m-1][m-2];
31 }
View Code

大教室传纸条(数据规模扩大,空间限制变小)

原文地址:https://www.cnblogs.com/wangzhebufangqi/p/12796185.html