通过将已经遍历的状态记录下来,从而减少重复的搜索量,这就是记忆化搜索。
动态规划的时候,记忆化搜索也是一种高效简洁的实现方式。
[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 2≤N,M≤100)的草地上游走, 试图找到整块草地中最美味的牧草。
Farmer John 在某个时刻看见贝茜在位置 ( R 1 , C 1 ) (R_1, C_1) (R1,C1),恰好 T T T( 0 < T ≤ 15 0 \lt T \leq 15 0<T≤15)秒后,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 24−17−16−1(从 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 1≤R,C≤100。
代码实现
#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;
}