Cpp环境【Tyvj1011】【Code[VS]1169】传纸条

【问题描述】

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

  在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。

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

【输入格式】  

  第一行有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

【数据范围】  

30%的数据满足:1<=m,n<=10
100%的数据满足:1<=m,n<=50

【来源】    

NOIP2008复赛提高组第3题
TYVJ1011 原题传送矩阵  重庆一中题库 原题传送矩阵  Code[VS]1169 原题传送矩阵

【思路梳理】

  万变不离其宗,变化的只是包装,始终不变、需要我们抓住的是状态。首先还是考虑30分的暴力回溯算法:
  分别从小轩的位置(m,n),小渊的位置(1,1)同时开始,寻找两条最优路径,使得该路径上所有点的点权值之和最大。一个值得注意的问题是需要考虑到两条路径除了起点和终点以外没有任何一个相同的点。由于来回的路径是可逆的,所以可以看成两个人同时从(1,1)走到(m,n),这样一来我们就需要对我们所走过的点进行标记后再递归调用,同时在递归调用以后要注意还原(清除这个标记),可以单线程地进行回溯(此时要注意标记问题),也可以双线程地进行回溯,预期的分:30分,时间复杂度这里写图片描述(伪代码后解释):

void back_tracking(int i,int x1,int y1,int x2,int y2)
{
    if(走到了终点)
    {
        择优选取权值和;
        return; 
    }

    保证两个人不会走到同一个点上
    if(!vis[x1+1][y1] && !vis[x2+1][y2])
    {
        vis[x1+1][y1]=true;
        vis[x2+1][y2]=true;
        back_tracking(i+1,x1+1,y1,x2+1,y2);
        vis[x1+1][y1]=false;
        vis[x2+1][y2]=false;
    }

    etc.     以此类推 
    总共是四种情况 
}

  关于时间复杂度的问题以及如何判断是否走到了终点(即是否可能出现小渊的信走到了小轩处、而小轩的信没有走到小渊处的情况)此处还要进行讨论。
  首先4为底数是不用去怀疑的,每次有四个方向的选择(其实大多数并没有那么多),但指数为什么是m+n-2?会不会出现上面所说的某一张纸条走到了终点另一张却没有?
  可以证明的是:经过m+n-2轮传递后,两张纸条严格地一定会同时到达终点。两条路径的长度是严格相等的。既然从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递,那么就不会存在回路、只能往靠近对方的方向移动,那么沿着路径横向移动的距离一定是m-1(这里写图片描述,两个点的横坐标之差),同理纵向移动的距离也就是n-1,两者之和等于m+n-2,即两点的曼哈顿距离。

100算法,动态规划:

状态函数f(i,j,x,y)=第一张纸条从(1,1)传到(i,j),第二张纸条从(1,1)传到(x,y)时最大的点权之和
状态转移方程:
这里写图片描述
边界条件:d[1][1][1][1]=0
答案:d[m][n][m][n]=第一张纸条从(1,1)传到(m,n),第二张纸条从(1,1)传到(m,n)时最大的点权之和

  需要值得注意的是实现该方程时要注意判断特殊情况:

     1. 不能够走到同一个点上
     2. 起点和终点不适用于上述规则

  可以给出Cpp代码如下,四重循环,时间复杂度为这里写图片描述,1s内能够完美解答此题:

void solve()
{
    for(int i=1;i<=m;i++)for(int j=1;j<=n;j++)
    for(int x=1;x<=m;x++)for(int y=1;y<=n;y++)
    {
         if(x==i==y==j==1) continue;//在起点
         else if(i==x && y==j && x==n && y==m)
          d[i][j][x][y]=max(d[i-1][j][x][y-1],d[i][j-1][x-1][y]);//走到终点
         else if(i==x && y==j && x!=m && y!=n)  d[i][j][x][y]=-inf;//两张纸条同时走到了除终点起点以外的同一个格子
        else
        {
            int t1,t2,t3,t4;
            t1=t2=t3=t4=-inf;   
            t1=d[i-1][j][x-1][y];
            t2=d[i][j-1][x-1][y];
            t3=d[i-1][j][x][y-1];
            t4=d[i][j-1][x][y-1];   
            d[i][j][x][y]=max(max(max(t1,t2),t3),t4)+a[i][j]+a[x][y];
        }

    }
        printf("%d",d[m][n][m][n]);
}

有志的OIer们,继续向下看!

  时间复杂度还是不尽人意,粗略估算一下后就会发现当m、n差不多达到84的时候这个算法就无法在1s内出来解了,而且此算法的空间复杂度同样较高。需要考虑一些更优的算法。
  根据上面所总结的路径长度等于两点曼哈顿距离我们可以将之推广到一般情况:从(1,1)点经过i次传递以后到达了的点行列坐标之和恒为定值:假设经过i次传递后到达了点(x,y)那么有x+y-2=i,即到达了点(x,i+2-x)。由此可以设出新的状态函数。具体解法详见【Cpp代码】。
  
状态函数:
  d(i,j,k)=纸条通过前i次传递后分别到达了(j,i-j+2)和(k,i-k+2)时的最大权值和

状态转移方程:
这里写图片描述

【Cpp代码】
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 55
using namespace std;
int n,m,a[maxn][maxn];
int d[maxn][maxn];

void dp()
{
    //d(i,j,k)=纸条通过前i次传递后分别到达了(j,i-j+2)和(k,i-k+2)时的最大权值和,可以使用滚动数组实现二维。  

    for(int i=1;i<=m+n-2;i++)
    for(int j=i+1;j>=1;j--)//使用滚动数组后倒着循环
    for(int k=i+1;k>=1;k--)//注意的是在传递i次的情况下我们最远能够传递到点(i+1,1)或者点(1,i+1)
    {
        int t1=0,t2=0,t3=0,t4=0;
        t1=d[j][k-1];
        t2=d[j-1][k];
        t3=d[j-1][k-1];
        t4=d[j][k];
        if(k==j)//如果走到了同一行上,因为总曼哈顿距离相同,所以一定在同一个点上
        {
            if(j==m)    d[j][k]=max(t1,t2);//在终点
            else continue;
        }
        else d[j][k]=max(max(t1,t2),max(t3,t4))+a[j][i-j+2]+a[k][i-k+2];
    }   
    cout<<d[m][m]<<endl;//注意状态函数的设置,理解为什么是两个m
}

int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
    for(int j=1;j<=n;j++)   scanf("%d",&a[i][j]);
    dp();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值