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

来源:互联网 发布:行者步行软件 编辑:程序博客网 时间:2024/06/05 19:59



方格取数

题目描述

设有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;}


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


                                             
2 1
原创粉丝点击