经典dp问题 矩阵取数以及变形

8人阅读 评论(0) 收藏 举报
分类:

再次总结一些比较经典的在矩阵中确定一个起始状态和结束状态, 问从起始到结束中走过的数要得到, 问最小(大), 路径输出等问题. 这些问题都可以转化为dp模型来解决. 下面以一些我做过的题进行分析.

入门级: 51Nod - 1083
问题: 给定一个二维矩阵, 问从左上角走到右下角可以取到的最大值是多少. 每次只能往下或者往右走.
这道题就非常简单了, dp[i][j] 代表走到(i, j)这个点的最大值是多少, 转移方程就是
dp[i][j] = dp[i-1][j] + dp[i][j-1]; O(n^2)的复杂度.
AC Code

const int maxn = 5e2+5;
int a[maxn][maxn], dp[maxn][maxn];
void solve()
{
    int n;
    cin >> n;
    for (int i = 1 ; i <= n ; i ++) {
        for (int j = 1 ; j <= n ; j ++) {
            scanf("%d", &a[i][j]);
        }
    }
    for (int i = 1 ; i <= n ; i ++) {
        for (int j = 1 ; j <= n ; j ++) {
            dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + a[i][j];
        }
    }
    cout << dp[n][n] << endl;
}

变形题 洛谷P1002
题意: 就是在一个二维矩阵中, 有一匹马, 马一次能跳到的点是他的控制点, 问从(0, 0)出发到达所给的终点的路径有多少条. 每次只能向下或者右走.
依旧dp, dp[i][j] 代表到达这个点的路径条数, 那么它依旧只能被上走两个进行更新, 只不过我们需要处理下马搜控制的点判一下不进行更新即可. 转移方程类似

AC Code

const int maxn = 50+5;
int a[maxn][maxn];
ll dp[maxn][maxn];
int dx[] = {-1, -1, -2, -2, 1, 1, 2, 2};
int dy[] = {2, -2, 1, -1, 2, -2, 1, -1};
void solve()
{
    int ex, ey, rx, ry;
    cin >> ex >> ey >> rx >> ry;
    for (int i = 0 ; i < 8 ; i ++) {
        int xx = rx + dx[i];
        int yy = ry + dy[i];
        if (xx < 0 || yy < 0) continue;
        a[xx][yy] = -1;
    }
    a[rx][ry] = -1;
    dp[0][0] = 1;
    for (int i = 0 ; i <= 20 ; i ++) {
        for (int j = 0 ; j <= 20 ; j ++) {
            if (a[i][j] == -1) continue;
            if (i) dp[i][j] = dp[i-1][j];
            if (j) dp[i][j] += dp[i][j-1];
        }
        if (dp[ex][ey]) break;
    }
    printf("%lld\n", dp[ex][ey]);
}

变形二: Uva - 10285
题意: 还是一个二维矩阵, 可以从任意一个点出发或者结束, 对应上面的点代表该点的海波高度, 每次只能向海拔高度低的走, 问可以走的最长的路有多长. 可以往四个方向走.

思路: 还是dp, dp[i][j] 代表走到这个点的最长路径是多少, 那么因为起始点可以任意, 所以我们需要进行一波预处理, 即我们把矩阵中的每一个数和位置先取出来, 然后按照从小到大进行排序, 每次取出来一个就进行在原图上的dp, 即判断四个方向中如果有比当前点小的点并且可以进行更新(即更优路径), 那么我们就更新这个点即可, 最后在所有的点中取一个max即可

AC Code

const int maxn = 1e2+5;
int dx[] = {1, -1, 0, 0};
int dy[] = {0, 0, 1, -1};
struct node {
    int x, y, w;
    bool operator <( const node& _) const {
        return w < _.w;
    }
}e[maxn*maxn];
int a[maxn][maxn], dp[maxn][maxn];
void solve()
{
    string s; int n, m;
    while(cin >> s >> n >> m) {
        int k = 0; Fill(a, -1); Fill(dp, 0);
        for (int i = 1 ; i <= n ; i ++) {
            for (int j = 1 ; j <= m ; j ++) {
                int x; scanf("%d", &x);
                e[++k] = node{i, j, x};
            }
        }
        sort(e+1, e+1+k); int ans = 0;
        for (int i = 1 ; i <= k ; i ++) {
            int x = e[i].x, y = e[i].y;
            a[x][y] = e[i].w; dp[x][y] = 1;
            for (int j = 0 ; j < 4 ; j ++) {
                int tmp = a[x+dx[j]][y+dy[j]];
                int val = dp[x+dx[j]][y+dy[j]] + 1;
                if (tmp != -1 && tmp < e[i].w && val > dp[x][y]) {
                    dp[x][y] = val;
                    ans = max(ans, dp[x][y]);
                }
            }
        }
        cout << s << ": " << ans << endl;
    }
}

经典问题 : Uva116
(这道题非常经典, 一定要记住这道题!)

题意: 还是一个二维矩阵, 每个点上有数, 从这个矩阵的第一列的任意位置出发到达最后一列的任意位置结束, 每次可以向下一列的上面一行, 当前行, 下面一行走, 并且注意在第一行时向上面一行走是走到最后一行, 同理最后一行也是, 问能取到的最小值是多少, 并且输出路径, 即每一列的行数, 如果有多解, 那么输出字典序最小的答案.

思路: 跟矩阵取数有点类似, 所以我们可以设dp[i][j]代表走到这个(i, j)的最小值是多少, 注意到最后要输出路径, 当然很简单记录一下此时的状态从哪个状态转移过来的即可, (但是我正着写死活过不了, 希望路过的大神贴一份正着写的代码给我学习学习…), 所以我们倒着推, 即从结束状态往起始状态推, 这样答案肯定不会变的,然后我们处理好边界问题即可, 和同时处理好字典序的问题, 详情请看代码实现:

AC Code

const int inf = 0x3f3f3f3f; //用这个可以直接mem
const ll INF = 1e18;
const int mod = 1e9+7;
const int maxn = 1e2+5;
int a[15][maxn], pre[15][maxn], dp[15][maxn];
int Next[15][maxn];
int path[maxn];
void solve()
{
    int n, m;
    while(~scanf("%d%d", &n, &m)) {
        for (int i = 1 ; i <= n ; i ++) {
            for (int j = 1 ; j <= m ; j ++) {
                scanf("%d", &a[i][j]);
            }
        }
        int st = 1;
        Fill(dp, inf); int ans = inf;
        for (int i = m ; i >= 1 ; i --) { // 倒着推, 方便记录路径
            for (int j = 1 ; j <= n ; j ++) {
                if (i == m) dp[j][i] = a[j][i]; // 更新初始状态
                else {
                    int r[3] = {j-1, j, j+1};
                    if (j == 1) r[0] = n; // 处理边界问题
                    if (j == n) r[2] = 1;
                    sort(r, r+3); // 这样就可以保证字典序最小啦.
                    for (int k = 0 ; k < 3 ; k ++) {
                        if (dp[r[k]][i+1] + a[j][i] < dp[j][i]) {
                            dp[j][i] = dp[r[k]][i+1] + a[j][i];
                            Next[j][i] = r[k];
                        }
                    }
                }
                if (i == 1 && dp[j][i] < ans) ans = dp[j][i], st = j;
                // 同样我们行是从小枚举的, 这样也就可以保证字典序乐.
            }
        }
        printf("%d", st);
        for (int i = 1 ; i < m ; i ++) {
            printf(" %d", Next[st][i]);
            st = Next[st][i];
        } // 打印路径即可
        printf("\n%d\n", ans);
    }
}

相似题: HDU - 5092
经典题的简化, 几乎和上面那道题一模一样, 就是变成了从第一行到最后一行, 并且字典序最大, 最后输出每一行的列数即可. 那么会了上面那道, 这道就是随便写写乐, 注意保持字典序最大即可.

这还是上海邀请赛的题, 应该是改编上面那道的, 所以记住经典模型是多么重要的啊……. (当时我用的bfs写的, 贼麻烦)

AC Code

const int inf = 0x3f3f3f3f; //用这个可以直接mem
const ll INF = 1e18;
const int mod = 1e9+7;
const int maxn = 100+5;
int a[maxn][maxn];
int dp[maxn][maxn], Next[maxn][maxn];
void solve()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1 ; i <= n ; i ++) {
        for (int j = 1 ; j <= m ; j ++) {
            scanf("%d", &a[i][j]);
        }
    }
    int st = 1, mi = inf; Fill(dp, inf);
    for (int i = n ; i >= 1 ; i --) { // 因为要打印路径, 所以还是倒着推.
        for (int j = m ; j >= 1 ; j --) { // 倒着可以保证起始点字典序最大.
            if (i == n) dp[i][j] = a[i][j];
            else { // 为了字典序最大就这样更新即可.
                if (j + 1 <= m && dp[i+1][j+1]  + a[i][j] < dp[i][j]) {
                    dp[i][j] = dp[i+1][j+1]  + a[i][j];
                    Next[i][j] = j+1;
                }
                if (dp[i+1][j]  + a[i][j] < dp[i][j]) {
                    dp[i][j] = dp[i+1][j]  + a[i][j];
                    Next[i][j] = j;
                }
                if (j - 1 >= 1 && dp[i+1][j-1]  + a[i][j] < dp[i][j]) {
                    dp[i][j] = dp[i+1][j-1]  + a[i][j];
                    Next[i][j] = j-1;
                }
            }
            if (i == 1 && dp[i][j] < mi) mi = dp[i][j], st = j;
        }
    }
    printf("%d", st);
    for (int i = 1 ; i < n ; i ++) {
        st = Next[i][st];
        printf(" %d", st);
    }
查看评论

动态规划基础篇之矩阵取数问题

给定一个m行n列的矩阵,矩阵每个元素是一个正整数,你现在在左上角(第一行第一列),你需要走到右下角(第m行,第n列),每次只能朝右或者下走到相邻的位置,不能走出矩阵。走过的数的总和作为你的得分,求最大...
  • lz161530245
  • lz161530245
  • 2017-08-07 16:10:08
  • 181

51NOD中的矩阵取数问题(1083,1084,1411)——动态规划,插头dp

唉,dp还是不行,多加努力。 1083 矩阵取数问题 基准时间限制:1 秒 空间限制:131072 KB 分值: 5 难度:1级算法题  收藏  关注 ...
  • say_c_box
  • say_c_box
  • 2016-08-08 15:37:50
  • 896

51nod1084 更难难得矩阵取数问题

分析: 只走一次的问题我们之前分析过。现在要走两次,有什么好办法呢? 我们设想,有两个人都从左上角走到右下角,这和走到终点再返回是一样的,我们可以dp么?怎么dp?先看看按照之前那种方法dp两次行不行...
  • yuanjunlai141
  • yuanjunlai141
  • 2016-05-24 18:42:05
  • 510

51nod 1084 更难的矩阵取数问题(DP)

原题链接:点击打开链接 一个M*N矩阵中有不同的正整数,经过这个格子,就能获得相应价值的奖励,先从左上走到右下,再从右下走到左上。第1遍时只能向下和向右走,第2遍时只能向上和向左走。两次...
  • u014679804
  • u014679804
  • 2015-10-04 22:06:37
  • 1138

51Nod 1083 矩阵取数问题 (DP)

给定一个m行n列的矩阵,矩阵每个元素是一个正整数,你现在在左上角(第一行第一列),你需要走到右下角(第m行,第n列),每次只能朝右或者下走到相邻的位置,不能走出矩阵。走过的数的总和作为你的得分,求最大...
  • yao1373446012
  • yao1373446012
  • 2016-08-19 09:56:02
  • 228

矩阵取数(动态规划理解)

首先这道题是一道动态规划的问题,而且这道题每行的取数得数之和都是互相独立的,那么我们思考这个问题时就可以从整个矩阵最大得数和简化为每行的最大得数和,那么我们就需要动态规划每一行的得数,而动态规划就是从...
  • jn_8316
  • jn_8316
  • 2016-07-22 10:55:52
  • 206

[51nod1411] 矩阵取数问题 V3

题目大意给定一个m行n列的矩阵,你可以从任意位置开始取数,到达任意位置都可以结束,每次可以走到的数是当前这个数上下左右的邻居之一,唯一的限制是每个位置只能经过一次,也就是说你的路径不自交。所经过的数的...
  • WorldWide_D
  • WorldWide_D
  • 2017-02-23 22:45:00
  • 375

51Nod 1083 矩阵取数问题(简单DP)

1083 矩阵取数问题 基准时间限制:1 秒 空间限制:131072 KB 分值: 5 难度:1级算法题  收藏  关注 一个N*N矩阵中有不同的正...
  • zwj1452267376
  • zwj1452267376
  • 2016-02-09 00:13:11
  • 872

51nod 1084:矩阵取数问题 V2

1084 矩阵取数问题 V2 基准时间限制:2 秒 空间限制:131072 KB 分值: 80 难度:5级算法题  收藏  关注 一个M*N矩阵中有不同的正整数,经过这个格子,就...
  • u010885899
  • u010885899
  • 2015-12-19 15:19:58
  • 921

51nod1083矩阵取数问题

这道题目是51nod教程的第一题,是一道非常简单的题目,之前做过了,再来刷一遍,因为是只能往下或者往右,所以每个点的值为左或上的最大值加自己#include #include using name...
  • zcmartin2014214283
  • zcmartin2014214283
  • 2016-04-24 20:55:02
  • 430
    个人资料
    持之以恒
    等级:
    访问量: 6万+
    积分: 5250
    排名: 6470
    文章分类