题解 [SHOI2002]滑雪
记忆化搜索$||dp||$剪枝
先讲方法,代码待会上
方法一:记忆化搜索
这个方法不怎么解释,就是每搜索完一个高度的最长路径记录一下,以后搜索其他的点时如果走到了这条路就直接用记录的值计算就是了
方法二:$dp$
假设一个点的高度为H,周围四个点的高度分别为$\mathcal{H1,H2,H3,H4,}$$f[i][j]$代表从$(i,j)$滑的最长长度。如果$\mathcal{H}$大于周围四个点的一个,那么就可以滑到那一个点,也就是$f$[高的那个点]$=f$[矮的那个点]$\mathcal{+1}$,到这里就不难写出状态转移方程了:$f[i][j]=f[i+b_g][j+c_g]+\mathcal{1}$(前提:$a[i+b_g][j+c_g]<a[i][j]$),其中$a$数组为读入的高度,$b$、$c$数组为方向数组,$f$数组为这个点的最长路径长度。注意在计算长度之前要先将高度排序,然后再来算
方法3:剪枝
假如说当前的最大高度为$maxn$,搜索函数里的参数$num$(代表当前已经走过的长度),当前这个点的高度为$height$。如果$num+height<=maxn$,那么可以直接$return$(注意:这里我还没有枚举周围四个点)。假设你从这个点可以一路无阻碍的走到$\mathcal{1}$(也就是最优的情况),那么从这个点走的长度为$height$。但是如果已经走的长度加上从这个点的最优长度都小于等于最大长度的话,那么搜下去肯定是毫无意义的,果断剪枝。
除了这个以外,还可以加一个小小的剪枝(与其说是剪枝,不如说是特判):如果$maxn==n*m$,那么就可以直接输出答案了。因为整个地图的大小为$n*m$,若$maxn==n*m$的话就说明可以跑完整个地图,那么最大值无论如何也不会更新了,所以说这里就可以直接输出了(说不定可以过某些测试点呢)
接着附上代码:(由于本人比较懒,$dp$的代码搬$(chao)$运$(xi)$的一本通)
方法一:$(\mathcal{42}ms/\mathcal{928}KB)$(不知道为什么我的记忆化是$\mathcal{42}ms$,大佬都是$\mathcal{0}ms$,可能我太蒟了吧)
#include<cstdio> #include<iostream> using namespace std; const int N=105; int n,m,a[N][N],f[N][N]= {0},maxn,ans,b[4]= {1,0,-1,0},c[4]= {0,1,0,-1}; bool p[N][N]; inline void dfs(int x,int y,int num) { int flag=0; for(int i=0; i<4; i++) { int xx=x+c[i],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<=n&&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() { 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++) { ans=0; p[i][j]=1; dfs(i,j,1); p[i][j]=0; f[i][j]=ans; if(maxn<ans) maxn=ans; } } printf("%d\n",maxn); return 0; }
方法二:$(\mathcal{33}ms/\mathcal{792}KB)$
#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; }
方法三:$(\mathcal{988}ms/\mathcal{1040}KB)$
#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; }