NOIP2008 传纸条(DP及滚动数组优化)

传送门

这道题有好多好多种做法呀……先说一下最暴力的,O(n^4的做法)

我们相当于要找两条从左上到右下的路,使路上的数字和最大。所以其实路径从哪里开始走并不重要,我们就直接假设全部是从左上出发的好啦。设dp[i][j][p][q]表示第一条路枚举到点(i,j),第二条路枚举到点(p,q)时,当前能取到的最大值。

这样dp方程很显然,就是dp[i][j][p][q] = max(dp[i-1][j][p-1][q],dp[i][j-1][p-1][q],sp[i-1][j][p][q-1],dp[i][j-1][p][q-1]) + a[i][j] + a[p][q].如果i==p && j == q,那么减去一个a[i][j].最后dp[m][n][m][n]即为结果。

四层循环枚举,复杂度O(n^4),上一下代码。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define rep(i,a,n) for(ll i = a;i <= n;i++)
#define per(i,n,a) for(ll i = n;i >= a;i--)
#define enter putchar('\n')

using namespace std;
const int M = 105;
typedef long long ll;

int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') op = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
    }
    return ans * op;
}
int m,n,dp[55][55][55][55],a[55][55];
int main()
{
    m = read(),n = read();
    rep(i,1,m)
    rep(j,1,n) a[i][j] = read();
    rep(i,1,m)
    rep(j,1,n)
    rep(p,1,m)
    rep(q,1,n) 
    {    
        dp[i][j][p][q] = max(max(dp[i-1][j][p-1][q],dp[i-1][j][p][q-1]),max(dp[i][j-1][p-1][q],dp[i][j-1][p][q-1])) + a[i][j] + a[p][q];
        if(i == p && j == q) dp[i][j][p][q] -= a[i][j];
    }
    printf("%d\n",dp[m][n][m][n]);
    return 0;
}

之后我们说一下怎么优化。两条路在走的时候,因为每次只能向右或者下走一格,所以两者必然是在同一条斜对角线上的(一条从右上到左下的对角线)那么我们就可以用对角线的横纵坐标和来表示当前DP到了哪里,之后每次只要枚举两个点的横坐标就可以把路径表示出来(也就是表示出来当前枚举的是哪两个点)。这样的话就可以把DP过程的时空降到三维的。

具体的写法也很简单,直接看一下代码即可。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')
using namespace std;
typedef long long ll;
const int M = 60;
int n,m,f[M][M],dp[120][M][M],q[M]; 
int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
    if(ch == '-') op = -1;
    ch = getchar();
    }
    while(ch >='0' && ch <= '9')
    {
    ans *= 10;
    ans += ch - '0';
    ch = getchar();
    }
    return ans * op;
}

int main()
{
    m = read(),n = read();
    rep(i,1,m)
    rep(j,1,n) f[i][j] = read();
    rep(i,2,n+m)
    {
    rep(p,1,i-1)
    {
            if(p > m) break;
        rep(q,1,i-1)
        {
                if(q > m) break;
        dp[i][p][q] = max(max(dp[i-1][p][q-1],dp[i-1][p-1][q]),max(dp[i-1][p-1][q-1],dp[i-1][p][q])) + f[p][i-p] + f[q][i-q];
        if(p == q) dp[i][p][q] -= f[p][i-p];
        }
    }
    }
    printf("%d\n",dp[n+m][m][m]);
    return 0;
}
    

之后,时间复杂度基本已经不能再优化,不过空间复杂度却可以优化到O(n^2)(其实还要再乘以一个常数)

我们从刚才的三维DP的转移过程来考虑一下。每次DP都是从上一条对角线上的元素转移过来,和其他的对角线没有任何关系,相当于我们在每次DP的时候只需要考虑两个,一个是i,一个是i-1,在i-1之前的其实已经没有了任何作用。所以我们完全可以废物再利用。因为每次i的值只改变1,所以可以使用按位与的方法把它变成0或者1,之后直接dp即可,而最后的答案就是dp[(m+n)&1][m][m].这样空间复杂度又会降一维。本题的数据范围小,如果数据范围较大,滚动数组对于空间的节省就极为有用了。

看一下代码。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')
using namespace std;
typedef long long ll;
const int M = 60;
int n,m,f[M][M],dp[2][M][M]; 
int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
    if(ch == '-') op = -1;
    ch = getchar();
    }
    while(ch >='0' && ch <= '9')
    {
    ans *= 10;
    ans += ch - '0';
    ch = getchar();
    }
    return ans * op;
}

int main()
{
    m = read(),n = read();
    rep(i,1,m)
    rep(j,1,n) f[i][j] = read();
    rep(i,2,n+m)
    {
    rep(p,1,i-1)
    {
        if(p > m) break;
        rep(q,1,i-1)
        {
        if(q > m) break;
        int k = (i-1)&1;
        dp[i&1][p][q] = max(max(dp[k][p][q-1],dp[k][p-1][q]),max(dp[k][p-1][q-1],dp[k][p][q])) + f[p][i-p] + f[q][i-q];
        if(p == q) dp[i&1][p][q] -= f[p][i-p];
        }
    }
    }
    printf("%d\n",dp[(n+m)&1][m][m]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/captain1/p/9544466.html