poj 1088/2111 滑雪(经典dp/字典序输出)

题意:一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。

思路:动态规划。正向递推和递归(记忆化搜索)方式都可以。用递推时注意需要将高度排序。需要注意一个细节,如果一个位置比它四周的位置都低,那么它的高度为1,而不是0.

递推:

#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define INF 0x3fffffff
using namespace std;
#define N 105
int dp[N][N],s[N][N];
struct point{
    int x,y,h;
}p[N*N];
int n,m,top = 0;
int ori[4][2] = {{-1,0},{1,0},{0,1},{0,-1}};
int cmp(struct point a,struct point b){
    return a.h < b.h;
}
int main(){
    int i,j,x,y,res=0;
    scanf("%d %d",&n,&m);
    memset(dp,0,sizeof(dp));
    memset(s, 0, sizeof(s));
    for(i = 1;i<=n;i++)
        for(j = 1;j<=m;j++){
            scanf("%d",&s[i][j]);
            p[top].x = i;
            p[top].y = j;
            p[top++].h = s[i][j];
        }
    sort(p,p+top,cmp);
    for(i = 0;i<top;i++){
        x = p[i].x;
        y = p[i].y;
        dp[x][y] = 1;//仔细理解题意很重要,贡献WA
        for(j = 0;j<4;j++)
            if(p[i].h > s[x+ori[j][0]][y+ori[j][1]])
                dp[x][y] = max(dp[x][y],dp[x+ori[j][0]][y+ori[j][1]]+1);
        res = max(res,dp[x][y]);
    }
    printf("%d\n",res);
    return 0;
}

记忆化搜索:

#include <stdio.h>
#include <string.h>
#define M 105
int flag[M][M];
int s[M][M];
int m,n;
int around[4][2] = {-1,0,0,-1,1,0,0,1};
int Max(int a,int b){
	return a<b?b:a;
}
int dp(int x,int y){
	int i,height = 0;
	if(flag[x][y])
		return flag[x][y];
	if(!x||!y||(x==n+1)||(y==m+1))
		return 0;
	for(i = 0;i<4;i++){
		int xx = x+around[i][0];
		int yy = y+around[i][1];
		if(s[x][y] >s[xx][yy])
			height = Max(dp(xx,yy),height);
	}
	return flag[x][y] = height+1;
}
int main(){
	int i,j,max;
	scanf("%d %d",&n,&m);
	memset(flag,0,sizeof(flag));
	memset(s,0,sizeof(s));
	for(i = 1;i<=n;i++)
		for(j = 1;j<=m;j++)
			scanf("%d",&s[i][j]);
	for(i = 1;i<=n;i++)
		for(j = 1;j<=n;j++)
			dp(i,j);
	for(max = 0,i = 1;i<=n;i++)
		for(j = 1;j<=m;j++)
			if(flag[i][j] > max)
				max = flag[i][j];
	printf("%d\n",max);
	return 0;
}

2111:题意与1088的区别:不是从高到低,而是从低到高爬坡;爬坡不是向四周,而是走“日”(象棋中的马走日);输出要求按照序列的字典序输出。

思路:仍然是dp没的说,需要考虑怎么输出。一开始怎么也转不过这个弯,认为从低到高更新,那么考虑字典序需要追溯到路径的开始才能比较。后来知道还是从高到低更新即可,这样自然保证了字典序。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
#define INF 0x3fffffff
#define clr(s,t) memset(s,t,sizeof(s))
#define N 365
int s[N][N],dp[N][N],pre[N][N],n,len;
struct node{
    int x,y,h;
}p[N*N];
int ori[8][2] = {{-2,-1},{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2}};
int cmp(node a,node b){
    return a.h > b.h;
}
int check(int x,int y){
    return x>=1&&y>=1&&x<=n&&y<=n;
}
int main(){
    int i,j,k,x,y;
    len = 0;
    clr(dp, 0);
    clr(pre, 0);
    scanf("%d",&n);
    for(i = 1;i<=n;i++)
        for(j = 1;j<=n;j++){
            scanf("%d",&s[i][j]);
            p[len].x = i;
            p[len].y = j;
            p[len++].h = s[i][j];
        }
    sort(p,p+len,cmp);//按照高度从高到低排序
    for(i = 0;i<len;i++){
        x = p[i].x;
        y = p[i].y;
        dp[x][y] = max(dp[x][y],1);//为了更新初始值
        for(j = 0;j<8;j++){
            int xx = x+ori[j][0];
            int yy = y+ori[j][1];
            if(check(xx, yy) && s[xx][yy]<s[x][y]){
                if(dp[xx][yy] <= dp[x][y]+1){
                    dp[xx][yy] = dp[x][y]+1;
                    pre[xx][yy] = j;
                }
            }
        }
    }
    k = dp[1][1];
    x = y = 1;
    for(i = 1;i<=n;i++)
        for(j = 1;j<=n;j++)
            if(dp[i][j]>k ||(dp[i][j]==k&&s[i][j]<s[x][y])){
                k = dp[i][j];
                x = i;
                y = j;
            }
    printf("%d\n",k);
    while(k--){
        printf("%d\n",s[x][y]);
        j = pre[x][y];
        x -= ori[j][0];
        y -= ori[j][1];
    }
    return 0;
}


你可能感兴趣的:(poj 1088/2111 滑雪(经典dp/字典序输出))