走两次的方格取数问题

问题 给定n行n列的方阵,每个元素是一个整数,从左上角走到右下角,每次只能向右或者下,走两次,使得得到的总和最大。同一次经过两个元素的话,总和只计算一次。


分析: 如果是只走一次的话,是经典的dp。走两次的话,dp两次是不对的……因为状态之间有影响,第一次和最大的路径可能会影响第二次的路径。这个题还有一个变形,是等价的,就是说从起点到终点只能向右和下,再从终点回起点只能向上或者左,使得总和最大。

           搜索是指数的算法,贪心不靠谱,两次dp也不行。所以还是尝试两个人同时走……,有个好处是,两个人一起走的话,只能在同一步经过相同的格子,这是由距离决定的。因为第step步,所在的格子距离起点的曼哈顿距离|x - x' | + |y - y'| = step……所以走一次时,状态是dp[(x,y)]由(x - 1, y) (x, y - 1)决定,现在状态是dp[(x1,y1)(x2,y2)]由4个状态确定即dp[(x1 - 1, y1)(x2 - 1, y2)] dp[(x1 - 1, x2, y2 - 1)] dp[(x1, y1 - 1)(x2 - 1, y2)] dp[(x1, y1 - 1) (x2 , y2 - 1)]确定。

const int N = 202;
const int inf = 1000000000;  //无穷大
int dp[N * 2][N][N];  
bool isValid(int step,int x1,int x2,int n) { //判断状态是否合法
int y1 = step - x1, y2 = step - x2;
    return ((x1 >= 0) && (x1 < n) && (x2 >= 0) && (x2 < n) && (y1 >= 0) && (y1 < n) && (y2 >= 0) && (y2 < n));
}

int getValue(int step, int x1,int x2,int n) {  //处理越界 不存在的位置 给负无穷的值
    return isValid(step, x1, x2, n)?dp[step][x1][x2]:(-inf);
}

//状态表示dp[step][i][j] 并且i <= j, 第step步  两个人分别在第i行和第j行的最大得分 时间复杂度O(n^3) 空间复杂度O(n^3) 
int getAnswer(int a[N][N],int n) {
int P = n * 2 - 2; //最终的步数
int i,j,step;
    
    //不能到达的位置 设置为负无穷大
    for (i = 0; i < n; ++i) {
        for (j = i; j < n; ++j) {
            dp[0][i][j] = -inf;
        }
    
    }
    dp[0][0][0] = a[0][0];

      for (step = 1; step <= P; ++step) {
        for (i = 0; i < n; ++i) {
            for (j = i; j < n; ++j) {
                dp[step][i][j] = -inf;
                if (!isValid(step, i, j, n)) { //非法位置
                    continue;
                }
                //对于合法的位置进行dp
                if (i != j) {
                    dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i - 1, j - 1, n));
                    dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i - 1, j, n));
                    dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i, j - 1, n));
                    dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i, j,n));
                    dp[step][i][j] += a[i][step - i] + a[j][step - j];  //不在同一个格子,加两个数
                }
                else {
                    dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i - 1, j - 1, n));
                    dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i - 1, j,  n));
                    dp[step][i][j] = max(dp[step][i][j], getValue(step - 1, i, j,  n));
                    dp[step][i][j] += a[i][step - i]; // 在同一个格子里,只能加一次
                }
                
            }
        }
    }
    return dp[P][n - 1][n- 1];
}

以上代码的复杂度空间复杂度是O(n^3),稍微想一下可以知道我们在推算dp[step]的时候 只有到了dp[step - 1],所以第一维,我们只开到2就可以了。就是step为奇数时,我们都用dp[1][i][j]表示状态,step为偶数我们都用dp[0][i][j]表示状态,这样我们只需要O(n^2)的空间,这就是滚动数组的方法。滚动数组写起来并不复杂,只需要对上面的代码稍作修改即可,优化后的代码如下:

int dp[2][N][N];

bool isValid(int step,int x1,int x2,int n) { //判断状态是否合法
int y1 = step - x1, y2 = step - x2;
    return ((x1 >= 0) && (x1 < n) && (x2 >= 0) && (x2 < n) && (y1 >= 0) && (y1 < n) && (y2 >= 0) && (y2 < n));
}

int getValue(int step, int x1,int x2,int n) {  //处理越界 不存在的位置 给负无穷的值
    return isValid(step, x1, x2, n)?dp[step % 2][x1][x2]:(-inf);
}

//状态表示dp[step][i][j] 并且i <= j, 第step步  两个人分别在第i行和第j行的最大得分 时间复杂度O(n^3) 使用滚动数组 空间复杂度O(n^2) 
int getAnswer(int a[N][N],int n) {
int P = n * 2 - 2; //最终的步数
int i,j,step,s;

    
    //不能到达的位置 设置为负无穷大
    for (i = 0; i < n; ++i) {
        for (j = i; j < n; ++j) {
            dp[0][i][j] = -inf;
        }
    
    }
    dp[0][0][0] = a[0][0];

      for (step = 1; step <= P; ++step) {
        for (i = 0; i < n; ++i) {
            for (j = i; j < n; ++j) {
                dp[step][i][j] = -inf;
                if (!isValid(step, i, j, n)) { //非法位置
                    continue;
                }
                s = step % 2;  //状态下表标
                //对于合法的位置进行dp
                if (i != j) {
                    dp[s][i][j] = max(dp[s][i][j], getValue(step - 1, i - 1, j - 1, n));
                    dp[s][i][j] = max(dp[s][i][j], getValue(step - 1, i - 1, j, n));
                    dp[s][i][j] = max(dp[s][i][j], getValue(step - 1, i, j - 1, n));
                    dp[s][i][j] = max(dp[s][i][j], getValue(step - 1, i, j,n));
                    dp[s][i][j] += a[i][step - i] + a[j][step - j];  //不在同一个格子,加两个数
                }
                else {
                    dp[s][i][j] = max(dp[s][i][j], getValue(step - 1, i - 1, j - 1, n));
                    dp[s][i][j] = max(dp[s][i][j], getValue(step - 1, i - 1, j,  n));
                    dp[s][i][j] = max(dp[s][i][j], getValue(step - 1, i, j,  n));
                    dp[s][i][j] += a[i][step - i]; // 在同一个格子里,只能加一次
                }
                
            }
        }
    }
    return dp[P % 2][n - 1][n- 1];
}




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值