HUT1673 传纸条

题义是两个人分别从左上角和右下角互相传递纸条,但是可以将问题转化为同时在左上角向下传递的两个纸条,求这两个不相交的路径的最大权值。

TLE了,但总算是把思维的DP弄懂了,总是在路线重复上觉得这个dp方程存在问题,后面才恍悟,在两个纸团坐标相同的点的dp值总是为零的,因为它从头到尾都没有更新。

定义的dp方程f[i][j][k][p]是指在1,1这个点同时抛的两个纸团的具体坐标(i,j)和(k,p),方程保证不更新两个点相同的值,方程是这样的:

f[i,j,k,p]:=max{f[i-1,j,k-1,p]
                    f[i-1,j,k,p-1]
                    f[i,j-1,k-1,p] 
                    f[i,j-1,k,p-1] 
         } + G[i,j] + G[k,p] 

网上有个三维优化的。。。

代码如下:

#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <algorithm>
#define MAXN 55
using namespace std;
  
int N, M, dp[MAXN][MAXN][MAXN][MAXN];
  
int G[MAXN][MAXN];
  
inline bool judge(int i, int j, int k, int p)
{
    if (i==k && j==p) {
        if (i==N && j==M) {
            return true;
        }
        else
            return false;
    }   
    else
        return true;
}
  
inline int Max(int i, int j, int k, int p)
{
    int mm = dp[i-1][j][k-1][p];
    mm = max(mm, dp[i-1][j][k][p-1]);
    mm = max(mm, dp[i][j-1][k-1][p]);
    mm = max(mm, dp[i][j-1][k][p-1]);
    return mm;
}
  
void DP()
{
    memset(dp, 0, sizeof (dp));
    for (int i = 1; i <= N; ++i) {
        for (int j = 1; j <= M; ++j) {
            for (int k = 1; k <= N; ++k) {
                for (int p = 1; p <= M; ++p) {
                    if (judge(i, j, k, p)) {
                    //  puts("JKLJL");
                        dp[i][j][k][p] = Max(i, j, k ,p) + G[i][j] + G[k][p];
                    }
                }
            }
        }
    }   
    printf("%d\n", dp[N][M][N][M]);
}
  
int main()
{ 
    while (scanf("%d %d", &N, &M) == 2) {
        for (int i = 1; i <= N; ++i) {
            for (int j = 1; j <= M; ++j) {
                scanf("%d", &G[i][j]);
            }
        }
        DP();
    } 
}

以下是三维的DP思路,由于从左上角走到右下角只有唯一的步数可能(step = N+M-2),只要知道走了多少步并且所在行数就能够推出所在的列数,因此,状态由四维变成了三维,f[s][i][j], s表示总共走了多少步,i表示第一个纸条所在的行数,j表示第二个纸条所在的行数,由前面已知有步数和行数便能够得出列数,所以dp方程和前面的是差不多的。

f[s][i][j] = max {

    f[s-1][i][j],      // 表示均从左边传递过来的纸条

    f[s-1][i-1][j],   // 表示第一个纸条从上面传递过来,第二个纸条从左边传递

    f[s-1][i][j-1],   // 表示第一个纸条从左边传递过来,第二个纸条从上面传递

    f[s-1][i-1][j-1]   // 表示均从上面传递过来

}

代码如下:

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAXN 55
using namespace std;
 
int N, M, G[MAXN][MAXN];
int dp[105][MAXN][MAXN];
 
// 定义了三维数组,f[s][i][j] s表示步数,i表示第一个纸团到达的行,j表示第二个纸团到达的行
 
inline bool judge(int s, int i, int j)
{
    int y1 = s - i + 2;
    int y2 = s - j + 2; // 这里求出了两个纸团所在位置的纵坐标
    if (y1 > M || y2 > M || y1 < 1 || y2 < 1) {
        return false;
    }
    if (i == j) {
        if (s == N + M - 2 && i == N) {
            return true;
        }
        else { 
            return false;
        }
    }
    else {
        return true;
    }
}
 
inline int Max(int s, int i, int j)
{
    int mm = dp[s-1][i][j];
    mm = max(mm, dp[s-1][i-1][j]);
    mm = max(mm, dp[s-1][i][j-1]);
    mm = max(mm, dp[s-1][i-1][j-1]);
    return mm;
}
 
void DP()
{
    int step = N + M - 2;
    memset(dp, 0, sizeof (dp));
    dp[0][1][1] = 2 * G[1][1];
    for (int s = 1; s <= step; ++s) { // 总共走step步
        for (int i = 1; i <= N; ++i) {
            for (int j = 1; j <= N; ++j) {
                if (judge(s, i, j)) {
                    dp[s][i][j] = Max(s, i, j)+G[i][s-i+2]+G[j][s-j+2];
                }
            }
        }
    }
    printf("%d\n", dp[step][N][N]);
}
 
int main()
{
    while (scanf("%d %d", &N, &M) == 2) {
        for (int i = 1; i <= N; ++i) {
            for (int j = 1; j <= M; ++j) {
                scanf("%d", &G[i][j]);
            }
        }
        DP();
    }
    return 0;
}
原文地址:https://www.cnblogs.com/Lyush/p/2538900.html