算法题(206):方格取数(动态规划)
审题:
本题需要我们找到两条从起点到终点的路径,使得这两条路径取得的路径和最大,走过的路的数就被取走了,无法再次获得思路:
方法一:贪心+动态规划
贪心方案:直接走两次该矩阵,每次走都按照寻找最大路径和的方法走
该方案存在问题,因为可以举出反例
图示:
这是按照贪心方案走的两条路线,我们发现这样会漏掉一个左下角的1
接下来我们看看最优解路线
最优解情况可以把左下角的1也取到
方法二:动态规划(1)状态表示:f[s][i1][i2]表示横纵坐标之和为s的时候走到(i1,j1)与(i2,j2)点的最大路径和
由于分开走是行不通的,所以我们这次尝试同时走,就是有两条路径(路径1与路径2),且由于他们是同时从同一个起点出发的所以他们的初始横纵坐标和与走的步数都是相同的(走一步则横纵坐标和++)
故:i1 + j1 = i2 + j2 = s
那么我们其实可以将状态表示为f[i1][j1][i2][j2],不过这是四维的,且有很多非法坐标需要排除,所以我们尝试将状态表示降维
根据上面的等式,我们知道j1和j2其实可以根据i1和i2求出来
即:j1 = s - i1 j2 = s - i2
也就是说只要确定了s,i1,i2那么两个坐标的位置其实也确定了,三维的状态表示最终就可以表示为f[s][i1][i2]
(2)状态转移方程:
由于这次有两个路径所以我们分析的时候也要依次分析
根据最后一步分析法,当前路径和一定是由走了s-1步的路径和+当前位置的值得来的,所以我们可以确定第一个参数一定是s-1
然后我们根据行走规则进行后面的判断,总共分四种走法(左侧表示i1,右侧表示i2)
注意:这里的方位表示要走的路径相对于目标位置的方位
1.左+左 :
由于都是在(i,j)的左侧向右侧移动一步,移动节点和目标节点处于同一行,所以第二和第三个参数分别直接等于i1,i2
即:f[s-1][i1][i2]
2.左+上
这里移动到(i1,j1)的节点在其左侧,故第二个参数仍为i1.然后移动到(i2,j2)的节点在其上方一行,故第三个参数为i2-1
3.上+左
4.上+上
3和4与上面同理分析即可
在将前面的最大路径和确定好之后,我们只需要再加上(i1,j1)与(i2,j2)的值就行。
注意:有可能i1 == i2,也就是这两个目标节点是同一个节点,此时由于值只能取得一次,所以我们要特殊判断,特殊处理
(3)初始化:
f数组初始化为0即可,因为数据值都是大于0的,所以0不会影响取值(4)填表顺序:从上到下,从左到右
还是依据行走规则来制定填表顺序
(5)答案输出:f[2*n][n][n]
解题:
#include<iostream> using namespace std; const int N = 15; int a[N][N], f[2 * N][N][N];//f[s][i1][i2]表示横纵坐标之和为s的时候走到(i1,j1)与(i2,j2)的最大路径和 int n,x,y,v; int main() {//数据录入cin >> n;while ((cin >> x >> y >> v, x && y && v)){a[x][y] = v;}//填表for (int s = 2; s <= 2 * n; s++)//sfor(int i1 = 1; i1 <= n; i1++)for (int i2 = 1; i2 <= n; i2++){int j1 = s - i1; int j2 = s - i2;if (j1 > n || j2 > n) continue;//非法访问int t1 = max(f[s - 1][i1][i2], f[s - 1][i1][i2 - 1]);int t2 = max(f[s - 1][i1 - 1][i2], f[s - 1][i1 - 1][i2 - 1]);if (i1 == i2){f[s][i1][i2] = max(t1, t2) + a[i1][j1];}else{f[s][i1][i2] = max(t1, t2) + a[i1][j1] + a[i2][j2];}}//输出答案cout << f[2 * n][n][n] << endl;return 0; }
1.录入数据需要注意,题目中说了节点值的录入当且仅当三个值都不为0的时候结束录入。
所以我们利用逗号表达式,实现录入数据的同时判断是否满足结束条件,满足就直接退出循环
2.s的范围是从2到2n,因为根据题目用例,我们发现左上角的起点是(1,1),所以起始的横纵坐标和就是2,结束的位置是(n,n)所以s的最大值就是2n
P1004 [NOIP 2000 提高组] 方格取数 - 洛谷