方格取数【暴搜 + DP + 记忆化搜索】

题目:

设有 N×N 的方格图 (N≤9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 0。如下图所示(见样例):

A
 0  0  0  0  0  0  0  0
 0  0 13  0  0  6  0  0
 0  0  0  0  7  0  0  0
 0  0  0 14  0  0  0  0
 0 21  0  0  0  4  0  0
 0  0 15  0  0  0  0  0
 0 14  0  0  0  0  0  0
 0  0  0  0  0  0  0  0
                         B

某人从图的左上角的 A 点出发,可以向下行走,也可以向右走,直到到达右下角的 B 点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 0)。
此人从 A 点到 B点共走两次,试找出 22 条这样的路径,使得取得的数之和为最大。

输入格式

输入的第一行为一个整数 N(表示 N×N 的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的 0 表示输入结束。

输出格式

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

暴搜:

由于回溯的时候,每个格子的值都没有被改变,所以此代码先记录在这里,并不能AC。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 15;
int w[N][N];    //权值矩阵!
int n;
int ans;
int res;

void dfs (int x, int y, int sum)    //递归走完所有的路径!
{
    if (x==n && y==n){
        ans = max(sum+w[x][y], ans);  //枚举所有的路径的最大值!
        return ;
    }
    int t = w[x][y];
    if (x + 1 <= n){
        sum += w[x][y];
        w[x][y] = 0;
        dfs(x+1, y, sum);
        sum -= t;
        w[x][y] = t;
    }
    if (y+1 <= n){
        sum += w[x][y];
        w[x][y] = 0;
        dfs(x, y+1, sum+w[x][y]);
        sum -= t;
        w[x][y] = t;
    }
}

int main()
{
    cin >> n;
    int a, b, c;
    while (cin >> a >> b >> c, a || b || c) w[a][b] = c;
    
    //因为是两条路线的嘛,那么就搜两次呗,一条路线先走,就先搜一次咯,搜的时候记得改变数组值
    dfs (1, 1, 0);
    
    res = ans;
    ans=0;
    cout << res << endl;
    dfs (1, 1, 0);
    
    res += ans;
    cout << res << endl;
    
    return 0;
}

动态规划:

  1. 如果只走一次的话,求从起点到终点的最大权值和应该为:f[i][j] = max(f[i-1][j], f[i][j-1])+w[i][j]; 即f[i][j]表示从起点走到 (i,j)的最大权值和,而本题是从起点走两次走到终点的最大权值和!
  2. 即状态表示为:f[i1][j1][i2][j2];这里,我们考虑假设两条路线是同时走的,则表示为:所有从起点(1,1)(1,1)出发,然后分别走到(i1,j1),(i2,j2)的最大权值和!
  3. 所以我们应该先考虑的是:什么时候会相遇即出现在同一个格子上!由于是同时走的,所以 某一时刻,两个人的步数都一定保持相同的!也只有在步数相同的情况下,才能够相遇!不然的话一个人走6步,一个走5步,那能相遇吗?显然不能!所以说根据本题的二维方格而言,步数相同的情况是:“i1 + j1 == i2 + j2”,如果两者相同,两条路径的格子才有可能重复!重合的时候,对于该格子,就只需要累加一次权值就可以了,否则累加两次权值,因为是不同的两个格子!
  4. 而由:i1 + j1 == i2 + j2可知,我们实际上是有一个等价的关系,即四个变量可以优化为三个变量,即 f[k, i1, i2],k表示两条线路当前走到的格子的横纵坐标之和。整体表示,所有从起点(1,1)(1,1)分别走到(i1,k-i1)(i2,k-i2)的路径的最大值。k=i1 + j1 = i2 + j2;实际上k是所走的步数和!k保证两条路径同步。
    K实际上是保证了同步,即两条路线当前都走了K步,步数相同才有可能重合!所以可将k看作是枚举步数!
  5. 状态表示已经确定了下来,现在思考它是如何转移过来的,单独一条路线的状态转移就是:往下走,或者往右走两个集合!所以说两条路线组合起来就会有4种情况,考虑每条路线的最后一步:
    第一条路线最后一步向下,第二条路线最后一步向下 = f[k][i1][i2] = f[k-1][i1-1][i2-1] + w[i][j];
    第一条路线最后一步向右,第二条路线最后一步向下= f[k][i1][i2] = f[k-1][i1][i2-1] + w[i][j];
    第一条路线最后一步向下,第二条路线最后一步向右= f[k][i1][i2] = f[k-1][i1-1][i2] + w[i][j];
    第一条路线最后一步向右,第二条路线最后一步向右= f[k][i1][i2] = f[k-1][i1][i2] + w[i][j];
    从上面这四个集合里面,选出最大值即为目标答案!

上图是第一种情况,都向下走
不难看出之所以以最后一步划分集合,是因为最后一步都是相同的,而到达(i-1,j)这一步的话,却有多种路径,所以说我们要求整条路的最大值,要先求到达:(i-1, j)的最大路径和,然后再加上 (i,j)的权值即可!
K只是保证步数相等,步数 = i1 + j1 = i2 + j2,所以我们可以用一个 K 去代替 j1,j2;

为什么不能分开走

因为第一次走的时候有可能有好几条路径都是第一次的解,而你分开走只能选择其中的一条。很不幸的是,第一次走过的地方会被重置成0,而你不确定的是第一次同样是最优解而未被你选择的情况下第二次的值会不会比你已经得出的答案要大。

这样不能得到最大值。因为第一遍的最优解可能有非常多种方案,你并不能确定采用哪一种。
第二遍的最大值依赖于第一遍选择哪种方案,而所有第一遍取最大值的方案数量可能是指数级别的。

关于不能分开走的理解:
因为第一次走的时候有可能有好几条路径都是第一次的解集,而你分开走只能选择其中的一条。很不幸的是,第一次走过的地方会被重置成0,而你不确定的是第一次同样是最优解而未被你选择的情况下第二次的值会不会比你已经得出的答案要大。

降维f[i1][j1][i2][j2]到f[k][i1][i2]解释:

在两条路径上的所有格子里,只有当横纵坐标之和相等时才有可能重合,受此启发我们可以将横纵坐标之和当做一维状态,这样可以将状态数量降至3维,且降维后每个状态可以递推出来。
相当于,两个人同时走,路径长度是一样的,k就是存储的就是路径长度再加上一个常数。

代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 15;
int w[N][N];    //权值矩阵
int n;
int f[N*2][N][N];
int main()
{
    cin >> n;
    
    int a, b, c;
    while (cin >> a >> b >> c, a || b || c) w[a][b] = c;
    
    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 <= n && j2 <= n && j1 >= 1 && j2 >= 1)
                {
                    int t = w[i1][j1];
                    if (i1 != i2) t += w[i2][j2];
                    int& x = f[k][i1][i2];
                    
                    x = max(x, f[k-1][i1-1][i2-1] + t);
                    x = max(x, f[k-1][i1-1][i2] + t);
                    x = max(x, f[k-1][i1][i2-1]  +t );
                    x = max(x, f[k-1][i1][i2] + t);
                }
            }
        }
    }
    printf("%d\n", f[n + n][n][n]);
    
    return 0;
}

记忆化搜索:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值