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