[SHOI2002]滑雪

题目描述
Michael喜欢滑雪。这并不奇怪,因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道在一个区域中最长的滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子:

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
一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可行的滑坡为24-17-16-1(从24开始,在1结束)。当然25-24-23-…-3-2-1更长。事实上,这是最长的一条。

输入格式
输入的第一行为表示区域的二维数组的行数R和列数C(1≤R,C≤100)。下面是R行,每行有C个数,代表高度(两个数字之间用1个空格间隔)。

输出格式
输出区域中最长滑坡的长度。

输入输出样例
输入
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

解题思路:
方法一:记忆化搜索

这个方法一般很容易想得到,就是每搜索完一个高度的最长路径记录一下,以后搜索其他的点时如果走到了这条路就直接用记录的值计算就是了

方法二:dp

假设一个点的高度为H,周围四个点的高度分别为H1,H2,H3,H4,f[i][j]代表从(i,j)滑的最长长度。如果H大于周围四个点的一个,那么就可以滑到那一个点,也就是f[高的那个点]=f[矮的那个点]+1,到这里就不难写出状态转移方程了:f[i][j]=f[i+bg][j+cg]+1(前提:a[i+bg][j+cg]<a[i][j]),其中a数组为读入的高度,b、c数组为方向数组,f数组为这个点的最长路径长度。注意在计算长度之前要先将高度排序,然后再来算

方法3:剪枝

假如说当前的最大高度为maxn,搜索函数里的参数num(代表当前已经走过的长度),当前这个点的高度为heightheight。如果num+height<=maxn,那么可以直接returnreturn(注意:这里我还没有枚举周围四个点)。假设你从这个点可以一路无阻碍的走到1(也就是最优的情况),那么从这个点走的长度为height。但是如果已经走的长度加上从这个点的最优长度都小于等于最大长度的话,那么搜下去肯定是毫无意义的,果断剪枝。

除了这个以外,还可以加一个小小的剪枝(与其说是剪枝,不如说是特判):如果maxnn∗m,那么就可以直接输出答案了。因为整个地图的大小为n∗m,若maxnn∗m的话就说明可以跑完整个地图,那么最大值无论如何也不会更新了,所以说这里就可以直接输出了(说不定可以过某些测试点呢)

代码:
方法一:

#include<bits/stdc++.h>
using namespace std;
const int N = 10100;
int r,m,res,maxn,ans;
int a[N][101];
bool p[N][101]={0};
int b[4]={1,0,-1,0};
int c[4]={0,1,0,-1};
int f[N][101]={0};
void dfs(int x , int y, int num)
{
	int flag = 0;
	for(int i = 0 ; i < 4 ; i++)
	{
		int xx = x+c[i];
		int yy = y+b[i];
		if(a[xx][yy]>=a[x][y])
		  continue;
		if(f[xx][yy])
		{
			ans = max(ans,f[xx][yy]+num);
			continue;
		}
		if(xx&&xx<=r&&yy&&yy<=m&&!p[xx][yy])
		{
			p[xx][yy] = 1;
			dfs(xx,yy,num+1);
			p[xx][yy] = 0;
		}
	}
	if(!flag)
	{
		ans = max(ans,num);
		return;
	}
}
int main()
{
	cin >> r >> m;
	for(int i = 1 ; i <= r ; i++)
	{
		for(int j = 1 ; j <= m ; j++)
		{
			cin >> a[i][j];
		}
	}
	for(int i = 1; i <= r ; i++)
	{
		for(int j = 1; j <= m ; j++ )
		{
			ans = 0;
			p[i][j] = 1;
			dfs(i,j,1);
			p[i][j] = 0;
			f[i][j] = ans;
			if(maxn < ans)
			  maxn = ans;
		}
	}
	cout << maxn << endl;
	return 0;
}

方法二:

#include<iostream>
#include<cstdio>
using namespace std;
int dx[5]= {0,-1,0,1,0},dy[5]= {0,0,1,0,-1};
long long r,c,i,j,p,t,ans;
long long m[101][101],f[101][101];
int search(int,int);
int main() {
    cin>>r>>c;
    ans=0;
    for(i=1; i<=r; i++) {
        for(j=1; j<=c; j++) cin>>m[i][j];
    }
    for(i=1; i<=r; i++) {
        for(j=1; j<=c; j++) {
            t=search(i,j);
            f[i][j]=t;
            if(t>ans) ans=t;
        }
    }
    cout<<ans<<endl;
}
int search(int x,int y) {
    int i,t,tmp,nx,ny;
    if(f[x][y]>0) return f[x][y];
    t=1;
    for(i=1; i<=4; i++) {
        nx=x+dx[i];
        ny=y+dy[i];
        if((nx>=1)&&(nx<=r)&&(ny>=1)&&(ny<=c)&&(m[x][y]<m[nx][ny])) {
            tmp=search(nx,ny)+1;
            if(tmp>t) t=tmp;
        }
    }
    f[x][y]=t;
    return t;
}

方法三:

#include<cstdio>
using namespace std;
const int N=105;
int n,m,a[N][N],maxn,b[4]= {1,0,-1,0},c[4]= {0,1,0,-1};
bool p[N][N]= {0};
inline void dfs(int x,int y,int num) {
    int flag=0;
    if(a[x][y]+num<maxn) return;
    for(int i=0; i<4; i++) {
        int xx=x+c[i],yy=y+b[i];
        if(xx>0&&xx<=n&&yy>0&&yy<=m) {
            if(a[xx][yy]>=a[x][y]) continue;//跳不过去
            if(!p[xx][yy]) {
                flag=1;
                p[xx][yy]=1;
                dfs(xx,yy,num+1);
                p[xx][yy]=0;
            }
        }
    }
    if(!flag) {
        if(maxn<num) maxn=num;
        return;
    }
}
int main() {
    scanf("%d %d",&n,&m);
    for(int i=1; i<=n; i++) {
         for(int j=1; j<=m; j++) scanf("%d",&a[i][j]);
    }
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m; j++) {
            if(maxn==n*m) {
                printf("%d\n",maxn);
                return 0;
            }
            p[i][j]=1;
            dfs(i,j,1);
            p[i][j]=0;
        }
    }
    printf("%d",maxn);
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值