滑雪
给定一个 𝑅 行 𝐶 列的矩阵,表示一个矩形网格滑雪场。
矩阵中第 𝑖 行第 𝑗 列的点表示滑雪场的第 𝑖 行第 𝑗 列区域的高度。
一个人从滑雪场中的某个区域内出发,每次可以向上下左右任意一个方向滑动一个单位距离。
当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。
下面给出一个矩阵作为例子:
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−2−1。
在给定矩阵中,最长的滑行轨迹为 25−24−23−…−3−2−1,沿途共经过 25 个区域。
现在给定你一个二维矩阵表示滑雪场各区域的高度,请你找出在该滑雪场中能够完成的最长滑雪轨迹,并输出其长度(可经过最大区域数)。
输入格式
第一行包含两个整数 𝑅 和 𝐶。
接下来 𝑅 行,每行包含 𝐶 个整数,表示完整的二维矩阵。
输出格式
输出一个整数,表示可完成的最长滑雪长度。
数据范围
1≤𝑅,𝐶≤300,
0≤矩阵中整数≤10000
输入样例:
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
算法分析
梳理一下题目含义
给定一个R 行 C列的矩形网格滑雪场
一个人从滑雪场中的某个区域内出发,每次可以向上下左右任意一个方向滑动一个单位距离。
当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。
注意
那个人可以从矩形网格滑雪场的任意一点出发
可以上下左右移动
每次只能滑一个单位距离
这个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度
本来是一个dfs的过程,遍历所有的位置,找到从当前位置往下走的最大路径,再取最大值,可是这样做会有很多重复的位置被重新计算过,因此可以利用空间换时间的思想,把遍历过的位置往下走的路径的最大值进行记录,这就是记忆化搜索
注意:f[][]
二维数组初始化的时候最好统一赋值为-1
,如果不进行初始化直接用0
判断,此题可以,可是如果遇到一些记忆化搜索的问题要求方案数的时候,初始化是0
可能会导致个别情况计算出来的恰好结果是0
时,却被认为未遍历过,因此统一赋值为-1
就没错了
时间复杂度 O(n2)
C++ 代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 310;
int n,m; //网格滑雪场的行和列
int f[N][N]; //状态转移式
int h[N][N]; //网格滑雪场
int dx[4] = {-1,0,1,0};
int dy[4] = {0,1,0,-1};
int dp(int x,int y){
int &v = f[x][y]; //Y总说的小技巧,等于把f[x][y]简化成了v,如果v发生变化,f[x][y]也会随之变化
if(v != -1) return v; //如果已经计算过了,就可以直接返回答案
v = 1; //注意v要先赋值为1哦~
for(int i = 0;i < 4;i ++){ //四个方向
int xx = x + dx[i];
int yy = y + dy[i];
if(xx >= 1 && xx <= n && yy >= 1 && yy <= m && h[x][y] > h[xx][yy]){ //判断这点是否能走
v = max(v,dp(xx,yy) + 1); //更新
}
}
return v; //别忘了返回v啊(被坑了
}
int main(){
cin>>n>>m; //输入滑雪场行和列
for(int i = 1;i <= n;i ++){
for(int j = 1;j <= m;j ++){
cin>>h[i][j]; //读入滑雪场数据
}
}
memset(f,-1,sizeof f);
int res = 0; //最后答案
for(int i = 1;i <= n;i ++){
for(int j = 1;j <= m;j ++){
//因为这个人可以在任意一点开始滑,所以要遍历一遍滑雪场
res = max(res,dp(i,j)); //更新答案
}
}
cout<<res<<endl;
return 0;
}
注意:
要记得把f 数组初始化为−1
定义v的时候注意要加上&
如果这个点没计算过,v记得要赋值为1
v=max(v,dp(xx,yy)+1) 不要忘了 +1
记得要返回v
Java 代码
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static int N = 310;
static int n,m;
static int[][] h = new int[N][N];
static int[][] f = new int[N][N];
static int[] dx = new int[] {0,-1,0,1};
static int[] dy = new int[] {-1,0,1,0};
static int dfs(int x,int y)
{
if(f[x][y] != -1) return f[x][y];
f[x][y] = 1;
for(int i = 0;i < 4;i ++)
{
int a = x + dx[i];
int b = y + dy[i];
if(a < 0 || a >= n || b < 0 || b >= m) continue;
if(h[x][y] > h[a][b]) f[x][y] = Math.max(f[x][y], dfs(a,b) + 1);
}
return f[x][y];
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
m = scan.nextInt();
for(int i = 0;i < n;i ++)
{
for(int j = 0;j < m;j ++)
h[i][j] = scan.nextInt();
}
for(int i = 0;i < n;i ++) Arrays.fill(f[i], -1);
int res = 0;
for(int i = 0;i < n;i ++)
{
for(int j = 0;j < m;j ++)
{
res = Math.max(res, dfs(i,j));
}
}
System.out.println(res);
}
}
题目:等和的分隔子集
在某客的一道关于记忆化搜索求方案数的问题
题目描述
算法分析
本身这题是一个dfs
的题目,从1到n中枚举,有两种方法,要么放左边left,要么放右边right,最终枚举完时若left == right
,则表示当前情况成立,ans ++
,可是N = 39,2^39远大于10^8
,因此直接dfs
会直接gg
掉,又由于枚举到当前状态x,left,right
时,当前状态成立的方案数一定是固定的,因此可以用一个数组存储当前状态的值,空间换时间,于是采用记忆化搜索的方法
初始时,f[0][0][0] = 1
;其他全是-1
,为了方便排除方案数刚刚是0的情况(例如当n == 37
时,就是方案数为0的情况),由于N = 39
,最大的累加和是(1 + 39) * 40 / 2 == 800
,分成左右两个集合,因此每个集合最多是400
,因此开f[40][400][400]
的空间
注意:由于左右集和总和相等时可以看成等价,因此求出来的结果会产生重复,因此需要除以2
时间复杂度
远小于40 * 400 * 400
Java 代码
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static int N = 410;
static int n;
static int ans = 0;
static long[][][] f = new long[40][N][N];
static long dfs(int x,int left,int right)
{
if(f[x][left][right] != -1) return f[x][left][right];
f[x][left][right] = 0;
if(x <= 0) return f[x][left][right];
if(left - x >= 0) f[x][left][right] += dfs(x - 1,left - x,right);
if(right - x >= 0) f[x][left][right] += dfs(x - 1,left,right - x);
return f[x][left][right];
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
int m = (1 + n) * n / 2;
for(int i = 0;i <= n;i ++)
for(int j = 0;j <= m / 2;j ++)
Arrays.fill(f[i][j], -1);
f[0][0][0] = 1;
System.out.println(dfs(n,m / 2,m / 2) / 2);
}
}
会超时的爆搜
时间复杂度O(2n)
Java 代码
import java.util.Scanner;
public class Main {
static int ans = 0;
static int n;
static void dfs(int x,int left,int right)
{
if(x == n + 1)
{
if(left == right) ans ++;
return ;
}
//放左边
dfs(x + 1,left + x,right);
//放右边
dfs(x + 1,left,right + x);
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
dfs(1,0,0);
System.out.println(ans / 2);
}
}