AYOJ-21523:方格取数--21491:传纸条 --双边dp 最新更新:剪枝!!



方格取数

题目描述

设有N*N的方格图(N<=10),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。
  某人从图的左上角的A 点(1,1)出发,可以向下行走,也可以向右走,直到到达右下角的B点(N,N)。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
  此人从A点到B 点共走两次,试找出2条这样的路径,使得取得的数之和为最大。

输入格式

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

输出

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

样例输入

8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0

样例输出

67



传纸条

题目描述

  小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标(1,1),小轩坐在矩阵的右下角,坐标(m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
  在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。

  还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用0表示),可以用一个0-100的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度只和最大。现在,请你帮助小渊和小轩找到这样的两条路径。

数据规模和约定
  30%的数据满足:1<=m,n<=10
  100%的数据满足:1<=m,n<=50


输入格式

  输入第一行有2个用空格隔开的整数m和n,表示班里有m行n列(1<=m,n<=50)。
  接下来的m行是一个m*n的矩阵,矩阵中第i行j列的整数表示坐在第i行j列的学生的好心程度。每行的n个整数之间用空格隔开。

输出

  输出一行,包含一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。


样例输入

3 3
0 3 9
2 8 5
5 7 0

样例输出

34



首先,这两题相似,都似乎双dp问题,但更重要的是他们的区别!请注意,一格是可以走重复路径,一个不可以!!!


先看《方格取数》

看图


图中的A、B的这一个状态表示某一步,我们可以认为A和B现在的位置为一个“状态”,那么这一个状态可能是有哪几个状态“转移”过来的呢?
如图中红色字体显示的。A和B分别都有两种可能的状态转移而来,那么状态(A,B)就有2*2=4个状态转移而来,分别如下:
(A1,B1),(A1,B2),(A2,B1),(A2,B2);,,,貌似这是排列组合。。。。
明白了这个那么状态转移方程就好写了:
dp(A,B)=max(dp(A1,B1),dp(A1,B2),dp(A2,B1),dp(A2,B2))+map(A)+map(B);map()表示这个格子的数值。转换成坐标就是:
dp[i][ii][j][jj]=max(dp[i][ii-1][j-1][jj],dp[i][ii-1][j-1][jj],dp[i-1][ii][j-1][jj],dp[i-1][ii][j][jj-1])+map[i][ii]+map[j][jj];
虽然是四维数组时间复杂度O(n^4)但是这个思路简单,对于测试数据比较弱的题目还是可以AC的(比如这一题,N<=10)

代码如下:


#include<algorithm>
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
using namespace std;
const int MAX=12;
int dp[MAX][MAX][MAX][MAX];
int mp[MAX][MAX];
int max(int a,int b)
{
    return a>b?a:b;
}
int maxi(int a,int b,int c,int d)
{
    return max(max(a,b),max(c,d));
}
int main()
{
    int n,i,ii,j,jj,x,y,w;
    cin>>n;
    memset(mp,0,sizeof(mp));
    while(cin>>x>>y>>w)
    {
        if(x==y && y==w && w==0)break;
        mp[x][y]=w;
    }
    int row=n,col=n;//本题为正方形,所以行和列都为n
    for(i=1; i<=row; i++)
    for(j=1; j<=row; j++)
    for(ii=1; ii<=col; ii++)
    for(jj=1; jj<=col; jj++)
    {
        if(i!=j || ii!=jj)//两次取的点不同
        {
            dp[i][ii][j][jj]=maxi(dp[i][ii-1][j][jj-1],dp[i][ii-1][j-1][jj],dp[i-1][ii][j][jj-1],dp[i-1][ii][j-1][jj])
                                  +mp[i][ii]+mp[j][jj];//需要加上这两点的值
        }
        if(i==j && ii==jj)//两次取的点相同
        {
            dp[i][ii][j][jj]=maxi(dp[i][ii-1][j][jj-1],dp[i][ii-1][j-1][jj],dp[i-1][ii][j][jj-1],dp[i-1][ii][j-1][jj])
                                  +mp[i][ii];//只需要加一次
        }
    }
    cout<<max(dp[row][col-1][row-1][col],dp[row-1][col][row][col-1])+mp[row][col]<<endl;//最后的一格程序没有包含,故要加上
    return 0;
}


考虑到时间复杂度高,可以想办法降低维数到 O(n^3)根据题意,每次只能向下和向右走,(i,j)向下就是(i+1,j) 向右是(i,j+1);有什么特点呢?可以发现行和列的和保持不变。令k=i+j;则状态转移方程可以转化为:dp[k][i][j]=max(dp[k-1][i-1][j],dp[k-1][i][j-1],dp[k-1][i-1][j-1],dp[k-1][i][j])+map[i][k-j]+map[j][k-i];注意此时,k的取值范围就扩大了一倍!!


#include<algorithm>
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
using namespace std;
const int MAX=12;
int dp[2*MAX][MAX][MAX];
int mp[MAX][MAX];
int max(int a,int b)
{
    return a>b? a:b;
}
int maxi(int a,int b,int c,int d)
{
    return max(max(a,b),max(c,d));
}
int main()
{
    int n,i,ii,j,jj,x,y,w;
    cin>>n;
    memset(mp,0,sizeof(mp));
    while(cin>>x>>y>>w)
    {
        if(x==y && y==w && w==0)break;
        mp[x][y]=w;
    }
    int row=n,col=n;//本题为正方形,所以行和列都为n
    int all=row+col;
    for(int k=1; k<=all; k++)//这里的k表示列(两列)i和j分别表示行,
    for(i=1; i<=row; i++)
    for(j=1; j<=row; j++)
    {
     if(i!=j && k>=i && k>=j)//两次走到不同点
     {
         dp[k][i][j]=maxi(dp[k-1][i][j],dp[k-1][i][j-1],dp[k-1][i-1][j],dp[k-1][i-1][j-1])
                        +mp[i][k-i]+mp[j][k-j];//注意这个地方不要写成mp[i][k-j]了,,wa了一次
     }
     if(i==j && k>=i && k>=j)//两次相同点
     {
          dp[k][i][j]=maxi(dp[k-1][i][j],dp[k-1][i][j-1],dp[k-1][i-1][j],dp[k-1][i-1][j-1])
                        +mp[i][k-i];//当i和j相等 ,只加一次
     }
    }
    cout<<max(dp[all-1][row-1][row],dp[all-1][row][row-1])+mp[row][col]<<endl;//最后一个点必须加上
    return 0;
}

然后是《传纸条》
基本思路一样,转化为从一个点出发,到达另一个点,特点是 
1,首尾两个点没有值;
2,路径不能重复;
这样,代码中就要去掉一格if语句,然后最后也不用加上终点的值。

 
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>

using namespace std;
int dp[105][52][52];
int mp[52][52];
int max(int a,int b){return a>b? a:b;}
int main()
{
    int N,i,j,k,row,col,all;
    scanf("%d",&N);
    while(N--)
    {
    scanf("%d%d",&row,&col);
    memset(dp,0,sizeof(dp));
    memset(mp,0,sizeof(mp));
    for(i=1; i<=row; i++)
    for(j=1; j<=col; j++)
        scanf("%d",&mp[i][j]);
    all=row+col;
    for(k=2; k<=all; k++)
    for(i=1; i<=row; i++)
    for(j=1; j<=row; j++)
    {
        if(i!=j && k>=i && k>=j )
        {
            int a= max( dp[k-1][i][j],dp[k-1][i-1][j-1]);
            int b= max( dp[k-1][i-1][j],dp[k-1][i][j-1]);
            dp[k][i][j]=max(a,b)+mp[i][k-i]+mp[j][k-j];
        }
    }
    cout<<max(dp[all-1][row-1][row],dp[all-1][row][row-1])<<endl;
    }
    return 0;
}
        



代码运行时间一百六十多毫秒,而后参照牛人的代码,可以剪枝的!!兴奋中。。。。

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>

using namespace std;
int dp[105][52][52];
int mp[52][52];
int max(int a,int b){return a>b? a:b;}
int main()
{
    int N,i,j,k,row,col,all;
    scanf("%d",&N);
    while(N--)
    {
        scanf("%d%d",&row,&col);
        memset(dp,0,sizeof(dp));
       // memset(mp,0,sizeof(mp));删掉
        for(i=1; i<=row; i++)
        for(j=1; j<=col; j++)
            scanf("%d",&mp[i][j]);
        all=row+col;
        for(k=3; k<all; k++)//优化了
        for(i=1; i<=row; i++)
        for(j=i+1; j<=row; j++)//优化了,因为双线的话,一定有一个条在另一条上边,横坐标加一
        {
    //        if(i!=j && k>=i && k>=j )
    //        {
    //            int a= max( dp[k-1][i][j],dp[k-1][i-1][j-1]);
    //            int b= max( dp[k-1][i-1][j],dp[k-1][i][j-1]);
    //            dp[k][i][j]=max(a,b)+mp[i][k-i]+mp[j][k-j];
    //        }
                if(k-i<1 || k-j<1)break; //这里因为i和j不表示坐标,而是两个移动坐标的横坐标
                if(k-i>col || k-j>col)continue;//有k为纵坐标的和,有k=row+col得,col=k-row;这里的i和j都表示row,因而得出col的取值范围
                                            //特别注意,这里k-i>col 不是 row !!
                int a=max(dp[k-1][i][j],dp[k-1][i-1][j-1]);//一下代码一样了
                int b=max(dp[k-1][i-1][j],dp[k-1][i][j-1]);
                dp[k][i][j]=max(a,b)+mp[i][k-i]+mp[j][k-j];
        }
        cout<<max(dp[all-1][row-1][row],dp[all-1][row][row-1])<<endl;
    }
    return 0;
}


时间减少了三分之二!可能还有优化的,望高人指导。




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值