AcWing 1027 方格取数

题目描述:

设有 N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:

2.gif

某人从图中的左上角 A 出发,可以向下行走,也可以向右行走,直到到达右下角的 B 点。

在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。

此人从 A 点到 B 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。

输入格式

第一行为一个整数N,表示 N×N 的方格图。

接下来的每行有三个整数,第一个为行号数,第二个为列号数,第三个为在该行、该列上所放的数。

一行“0 0 0”表示结束。

输出格式

输出一个整数,表示两条路径上取得的最大的和。

数据范围

N≤10

输入样例:

8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0

输出样例:

67

分析:

本题相当于将摘花生问题走了两遍,当然,我们不能调用摘花生问题两次计算解决了。与其说这个人从左上角走到右下角走了两次,不如理解为,两个人同时从起点出发,走到一个格子的数字只能被其中一个人所收集,求两个人都走到终点时收集到的数字之和的最大值。走一次时,我们用f[i][j]表示从(1,1)走到(i,j)的所有路径中收集到的数字之和的最大值,现在有两个人走,那么状态表示可以是f[i1][j1][i2][j2]表示两个人从(1,1)分别走到(i1,j1)和(i2,j2)的所有路径中收集到的数字之和的最大值。由于方格中的数字只能被一人所有,便奠定了二者的先后关系,也就是说,不论两个人是同时出发,还是相隔多久出发,最后的最优解都是一样的,不妨设两人速度一样,即经过相同的时间走过的路径长度一样,路径的长度k = i1 + j1 = i2 + j2。这样设有两个好处,其一是两个人到达同一个格子时数字只能被采集一次,而到达同一个格子走过的路径长度自然是一样大的;其二可以减小dp数组的维数,既然 i1 + j1 = i2 + j2 = k,那么就可以用f[k][i1][i2]表示一种状态了,数组的维数也就从四维减小到三维了。

对第一个人来说,可以从左边或者上边走到(i1,j1),第二个人也可能从左边或者上边走到(i2,j2),根据乘法原理得出一共有四种走法,即f[k][i1][i2]可以由四种状态转移而来。分别是左左:f[k-1][i1-1][i2-1]、上上:f[k-1][i1][i2]、左上:f[k-1][i1-1][i2]、上左:f[k-1][i1][i2-1],转移到f[k][i1][i2]时,如果i1 != i2,说明二者走到的不是同一个方格,可以取方格(i1,j1),(i2,j2)内的数,否则只能取一次。i1 != i2时走到当前状态增加的数字t = w[i1][j1] + w[i2][j2],i1 = i2时走到当前状态增加的数字t = w[i1][j1]。状态转移方程为f[k][i1][i2] = max(f[k-1][i1-1][i2-1],f[k-1][i1][i2],f[k-1][i1-1][i2],f[k-1][i1][i2-1]) + t。这里由于两人速度一致,因此两人到达同一方格只可能是同时,所以不用考虑其中一个人走到某个方格是另一个人在很早之前走过的了。

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 12;
int w[N][N],f[N + N][N][N];
int main(){
    int n,x,y,z;
    cin>>n;
    while(cin>>x>>y>>z,x || y || z) w[x][y] = z;
    for(int k = 2;k <= n + n;k++){
        for(int i1 = 1;i1 <= n;i1++){
            for(int i2 = 1;i2 <= n;i2++){
                int j1 = k - i1,j2 = k - i2;
                if(j1 > 0 && j1 <= n && j2 > 0 && j2 <= n){
                    int t = w[i1][j1];
                    if(i1 != i2)    t += w[i2][j2];
                    f[k][i1][i2] = max(max(f[k-1][i1-1][i2-1],f[k-1][i1][i2]),max(f[k-1][i1-1][i2],f[k-1][i1][i2-1])) + t;
                }
            }
        }
    }
    cout<<f[n + n][n][n]<<endl;
    return 0;
}

观察上面代码的状态转移方程,计算f[k][i1][i2]时仅用到了k - 1时的四种状态,因此考虑能否用滚动数组实现,显然是可以的,由于用到的全是上一层且i1,i2均不大于当前的坐标的状态,所以需要在枚举i1,i2时自大到小枚举,这样就不会导致还需要使用的数据被覆盖的情况了。举个例子,之前在计算f[4][2][2]时用到了f[3][1][1],f[3][2][2],f[3][1][2],f[3][2][1]四种状态,使用滚动数组意味着用k=3时的f[1][1],f[2][2],f[1][2],f[2][1]计算出了k = 4时的f[2][2],这时k=3时的f[2][2]已经被覆盖了,后面在计算k = 4时的f[3][2]时还需要用到k = 3时的f[2][2],就会引起错误。而倒着计算状态,i1,i2小的不会使用比它们大的状态,就完全没问题了。

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 12;
int w[N][N],f[N][N];
int main(){
    int n,x,y,z;
    cin>>n;
    while(cin>>x>>y>>z,x || y || z) w[x][y] = z;
    for(int k = 2;k <= n + n;k++){
        for(int i1 = n;i1 >= 1;i1--){
            for(int i2 = n;i2 >= 1;i2--){
                int j1 = k - i1,j2 = k - i2;
                if(j1 > 0 && j1 <= n && j2 > 0 && j2 <= n){
                    int t = w[i1][j1];
                    if(i1 != i2)    t += w[i2][j2];
                    f[i1][i2] = max(max(f[i1-1][i2-1],f[i1][i2]),max(f[i1-1][i2],f[i1][i2-1])) + t;
                }
            }
        }
    }
    cout<<f[n][n]<<endl;
    return 0;
}
发布了272 篇原创文章 · 获赞 26 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_30277239/article/details/104022157