CPP【北京大学暑期课《ACM/ICPC竞赛训练》】高山滑雪

本文介绍了如何用动态规划和记忆化搜索解决高山滑雪问题,这是一个在棋盘上寻找最长路径的问题,同时涉及到DAG图。通过状态函数f(x,y)表示从(x,y)出发的最长路径长度,对边界情况进行分析,并提供了一个有效的CPP代码实现。" 107806939,9972169,c++实现最长不下降子序列详解,"['c++', '算法']
摘要由CSDN通过智能技术生成
【问题描述】  
  Michael喜欢滑雪这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。  
  Michael想知道载一个区域中最长的滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子。
  一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。

 【输入格式】  
  输入的第一行表示区域的行数R和列数C。下面是R行,每行有C个整数,代表高度h,0<=h<=10 000 000。
 
 【输出格式】  
  输出最长区域的长度。
    
 【输入样例】   
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
    
 【输出样例】   
25
    
 【数据范围】   

1 <= R,C <= 500


【思路分析】

        从给出的样例不难看出:这是一道棋盘上的最长路径问题,同时也是有向无环图(不可能存在“A比C高,C又比A高”的情况)。

       实现方法有很多,一种是BFS搜索算法,依次从每一个点出发进行BFS,搜索其能走出的最远距离。这样做显然会超时,时间复杂度达到O(R*C*2000*R*C)。因此,考虑对其进行优化:既然有些线路会多次重复经过,那么的话可以通过记忆化搜索,对于每一次已经遍历过的路径,直接查表即可。由此一来,我们连BFS也可以省略:直接利用动态规划的记忆化搜索即可,列出状态函数:f(x,y)=从(x,y)出发的最长路径长度。状态转移方程如下:

接着,对边界进行分析。显然,当我们已经处于一个四周点的权值都比他高(即DAG图的终端节点,出度为0)的点(x,y)时,f(x,y)=1。此题不需要建图,直接运用记忆化搜索、填表、查表就可以完美解答。最后我们直接搜索最大的一个f(x,y)即可。


      另一种思路是拓扑排序,建图,然后对于点(x,y),周围每有一个权值更高的点,(x,y)的入度就增加1,然后直接按照拓扑排序的顺序排列即可。对于这个题,显然思路二较思路一时间复杂度更高,所以笔者仅给出第一种思路的解题代码。


【CPP代码】

#include<cstdio>
#include<iostream>
#include<cstring>
#define maxn 505
using namespace std;
int dx[]={-1,1,0,0};
int dy[]={0,0,-1,1};
int r,c,rd[maxn][maxn],d[maxn][maxn],ans=0;
int height[maxn][maxn];

int f(int x,int y)
{
    if(d[x][y]!=-1)    return d[x][y];
    if(rd[x][y]==0)     return  d[x][y]=1;//入度为0,周围没有更高的点
    int cnt=0;
    for(int i=0;i<4;i++)
    {
        int xx=x+dx[i],yy=y+dy[i];
        if(xx>r || xx<1 || yy>c || yy<1 || height[xx][yy]<=height[x][y])    continue;//判断越界
        cnt=max(cnt,f(xx,yy)+1);//从四周路径长度最大的一个较低点转移过来,显然路径长度要增加1
    }    
    d[x][y]=cnt;//填表
    return cnt;
}

int main()
{
    //freopen("in.txt","r",stdin);
    memset(d,-1,sizeof(d));
    cin>>r>>c;
    for(int i=1;i<=r;i++)
    for(int j=1;j<=c;j++)
        scanf("%d",&height[i][j]);
    
    for(int i=1;i<=r;i++)
    for(int j=1;j<=c;j++)
    for(int k=0;k<4;k++)
    {
        int x=i+dx[k],y=j+dy[k];
        if(x>r || x<1 || y>c || y<1)    continue;
        if(height[i][j]>height[x][y])    rd[x][y]++;
    }
    
    for(int i=1;i<=r;i++)
    for(int j=1;j<=c;j++)
        ans=max(ans,f(i,j));
        
    cout<<ans;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值