Part 3.3 记忆化搜索

通过将已经遍历的状态记录下来,从而减少重复的搜索量,这就是记忆化搜索。

动态规划的时候,记忆化搜索也是一种高效简洁的实现方式。

[NOIP2010 提高组] 引水入城

题目背景

NOIP2010 提高组 T4

题目描述

在一个遥远的国度,一侧是风景秀美的湖泊,另一侧则是漫无边际的沙漠。该国的行政区划十分特殊,刚好构成一个 N N N M M M 列的矩形,如上图所示,其中每个格子都代表一座城市,每座城市都有一个海拔高度。

为了使居民们都尽可能饮用到清澈的湖水,现在要在某些城市建造水利设施。水利设施有两种,分别为蓄水厂和输水站。蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的蓄水池中。

因此,只有与湖泊毗邻的第 1 1 1 行的城市可以建造蓄水厂。而输水站的功能则是通过输水管线利用高度落差,将湖水从高处向低处输送。故一座城市能建造输水站的前提,是存在比它海拔更高且拥有公共边的相邻城市,已经建有水利设施。由于第 N N N 行的城市靠近沙漠,是该国的干旱区,所以要求其中的每座城市都建有水利设施。那么,这个要求能否满足呢?如果能,请计算最少建造几个蓄水厂;如果不能,求干旱区中不可能建有水利设施的城市数目。

输入格式

每行两个数,之间用一个空格隔开。输入的第一行是两个正整数 N , M N,M N,M,表示矩形的规模。接下来 N N N 行,每行 M M M 个正整数,依次代表每座城市的海拔高度。

输出格式

两行。如果能满足要求,输出的第一行是整数 1 1 1,第二行是一个整数,代表最少建造几个蓄水厂;如果不能满足要求,输出的第一行是整数 0 0 0,第二行是一个整数,代表有几座干旱区中的城市不可能建有水利设施。

样例 #1

样例输入 #1

2 5
9 1 5 4 3
8 7 6 1 2

样例输出 #1

1
1

样例 #2

样例输入 #2

3 6
8 4 5 6 4 4
7 3 4 3 3 3
3 2 2 1 1 2

样例输出 #2

1
3

提示

样例 1 说明

只需要在海拔为 9 9 9 的那座城市中建造蓄水厂,即可满足要求。

样例 2 说明

上图中,在 $3 $ 个粗线框出的城市中建造蓄水厂,可以满足要求。以这 $3 $ 个蓄水厂为源头在干旱区中建造的输水站分别用 3 3 3 种颜色标出。当然,建造方法可能不唯一。

数据范围

本题有 10 个测试数据,每个数据的范围如下表所示:

测试数据编号能否满足要求 N ≤ N\le N M ≤ M\le M
1不能 10 10 10 10 10 10
2不能 100 100 100 100 100 100
3不能 500 500 500 500 500 500
4 1 1 1 10 10 10
5 10 10 10 10 10 10
6 100 100 100 20 20 20
7 100 100 100 50 50 50
8 100 100 100 100 100 100
9 200 200 200 200 200 200
10 500 500 500 500 500 500

对于所有 10 个数据,每座城市的海拔高度都不超过 1 0 6 10^6 106

#include<iostream>
#include<cstring>
using namespace std;
#define MAX_N 500
int arr[MAX_N+5][MAX_N+5];
int n,m;
int dir1[]={0,-1,0,1,0},dir2[]={0,0,-1,0,1,0};
int dp[3][MAX_N+5][MAX_N+5];
int vis[MAX_N+5][MAX_N+5];
void dfs(int row,int col)
{
	vis[row][col]=1;
	for(int i=1;i<=4;i++)
	{
		int nxtrow=row+dir1[i],nxtcol=col+dir2[i];
		if(nxtrow<1||nxtrow>n||nxtcol<1||nxtcol>m)continue;
		if(arr[nxtrow][nxtcol]>=arr[row][col])continue;
		if(!vis[nxtrow][nxtcol])dfs(nxtrow,nxtcol);
		dp[1][row][col]=min(dp[1][row][col],dp[1][nxtrow][nxtcol]);
		dp[2][row][col]=max(dp[2][row][col],dp[2][nxtrow][nxtcol]);
	}
	return ;
}
int main()
{
	memset(dp[1],0x3f,sizeof dp[1]);
	memset(dp[2],0,sizeof dp[2]);
	cin>>n>>m;
	for(int i=1;i<=m;i++)dp[1][n][i]=dp[2][n][i]=i;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	cin>>arr[i][j];
	for(int i=1;i<=m;i++)
	if(!vis[1][i])
	dfs(1,i);
	int cnt=0;
	for(int i=1;i<=m;i++)
	{
		if(!vis[n][i])cnt++;	
	}
	if(cnt)
	{
		cout<<0<<endl<<cnt<<endl;
		return 0;
	}
	int left=1;
	while(left<=m)
	{
		int right=0;
		for(int i=1;i<=m;i++)
		if(dp[1][1][i]<=left)
		{
			right=max(right,dp[2][1][i]);
		}
		left=right+1;
		cnt++;
	}
	cout<<1<<endl<<cnt<<endl;
	return 0;
}

[USACO08MAR] Cow Travelling S

题目描述

奶牛们在被划分成 N N N M M M 列( 2 ≤ N , M ≤ 100 2 \leq N,M \leq 100 2N,M100)的草地上游走, 试图找到整块草地中最美味的牧草。

Farmer John 在某个时刻看见贝茜在位置 ( R 1 , C 1 ) (R_1, C_1) (R1,C1),恰好 T T T 0 < T ≤ 15 0 \lt T \leq 15 0<T15)秒后,FJ 又在位置 ( R 2 , C 2 ) (R_2, C_2) (R2,C2) 与贝茜撞了正着。FJ 并不知道在这 T T T 秒内贝茜是否曾经到过 ( R 2 , C 2 ) (R_2, C_2) (R2,C2),他能确定的只是,现在贝茜在那里。

S S S 为奶牛在 T T T 秒内从 ( R 1 , C 1 ) (R_1, C_1) (R1,C1) 走到 ( R 2 , C 2 ) (R_2, C_2) (R2,C2) 所能选择的路径总数,FJ 希望有一个程序来帮他计算这个值。每一秒内,奶牛会水平或垂直地移动 1 1 1 单位距离(奶牛总是在移动,不会在某秒内停在它上一秒所在的点)。草地上的某些地方有树,自然,奶牛不能走到树所在的位置,也不会走出草地。

现在你拿到了一张整块草地的地形图,其中 . 表示平坦的草地,* 表示挡路的树。你的任务是计算出,一头在 T T T 秒内从 ( R 1 , C 1 ) (R_1, C_1) (R1,C1) 移动到 ( R 2 , C 2 ) (R_2, C_2) (R2,C2) 的奶牛可能经过的路径有哪些。

输入格式

第一行包含 3 3 3 个用空格隔开的整数: N , M , T N,M,T N,M,T

接下来 N N N 行:第 i i i 行为 M M M 个连续的字符,描述了草地第 i i i 行各点的情况,保证字符是 .* 中的一个。

最后一行 4 4 4 个整数 R 1 , C 1 , R 2 , C 2 R_1,C_1,R_2,C_2 R1,C1,R2,C2

输出格式

输出从 ( R 1 , C 1 ) (R_1, C_1) (R1,C1) 移动到 ( R 2 , C 2 ) (R_2, C_2) (R2,C2) 的方案数。

样例 #1

样例输入 #1

4 5 6
...*.
...*.
.....
.....
1 3 1 5

样例输出 #1

1

提示

奶牛在 6 6 6 秒内从 ( 1 , 3 ) (1,3) (1,3) 走到 ( 1 , 5 ) (1,5) (1,5) 的方法只有一种,绕过她面前的树。

代码实现

#include<iostream>
#include<cstring>
using namespace std;
#define MAX_N 100
#define MAX_T 15
char arr[MAX_N+5][MAX_N+5];
int n,m,t;
int r1,c1,r2,c2;
int dir1[5]={0,-1,0,1,0},dir2[5]={0,0,-1,0,1};
int dp[MAX_N+5][MAX_N+5][MAX_T+5]={0};
int dfs(int row,int col,int step)
{
	if(step==0)
	{
		if(row==r2&&col==c2)
		return dp[row][col][0]=1;
		return dp[row][col][0]=0;
	}
	int temp=0;
	for(int i=1;i<=4;i++)
	{
		int nxtrow=row+dir1[i],nxtcol=col+dir2[i];
		if(nxtrow<1||nxtrow>n||nxtcol<1||nxtcol>m)continue;
		if(arr[nxtrow][nxtcol]=='*')continue;
		if(r2-nxtrow+c2-nxtcol>step)continue;
		if(dp[nxtrow][nxtcol][step-1]!=-1)
		temp+=dp[nxtrow][nxtcol][step-1];
		else temp+=dfs(nxtrow,nxtcol,step-1);
	}
	return dp[row][col][step]=temp;
}
int main()
{
	memset(dp,-1,sizeof dp);
	cin>>n>>m>>t;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	cin>>arr[i][j];
	cin>>r1>>c1>>r2>>c2;
	cout<<dfs(r1,c1,t);
	return 0;
 } 

[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-17-16-1 2417161(从 24 24 24 开始,在 1 1 1 结束)。当然 25 25 25 24 24 24 23 23 23 … \ldots 3 3 3 2 2 2 1 1 1 更长。事实上,这是最长的一条。

输入格式

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

输出格式

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

样例 #1

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

样例输出 #1

25

提示

对于 100 % 100\% 100% 的数据, 1 ≤ R , C ≤ 100 1\leq R,C\leq 100 1R,C100

代码实现

#include<iostream>
using namespace std;
#define MAX_N 100	
int r,c;
int ans=0;
int arr[MAX_N+5][MAX_N+5];
int dp[MAX_N+5][MAX_N+5];
int dir1[]={0,-1,0,1,0},dir2[]={0,0,-1,0,1};
int dfs(int row,int col)
{
	if(dp[row][col]!=0)return dp[row][col];
	int temp=1;
	for(int i=1;i<=4;i++)
	{
		int nxtrow=row+dir1[i],nxtcol=col+dir2[i];
		if(nxtrow<1||nxtrow>r||nxtcol<1||nxtcol>c)continue;
		if(arr[nxtrow][nxtcol]>=arr[row][col])continue;
		temp=max(temp,dfs(nxtrow,nxtcol)+1); 
	}
	return dp[row][col]=temp;
}
int main()
{

	cin>>r>>c;
	for(int i=1;i<=r;i++)
	for(int j=1;j<=c;j++)
	cin>>arr[i][j];
	for(int i=1;i<=r;i++)
	for(int j=1;j<=c;j++)
	ans=max(ans,dfs(i,j));
	cout<<ans;
	return 0;
 } 
  • 28
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值