1、题目描述
小明和小红是两位魔法师,他们一起在一张N*N的方格地图上冒险(N<=9)去击败魔王,地图中的方格代表他们所走的路径,方格中的正整数代表可获得的法力值,而其他的方格中的数字为0代表无法获得法力值,如下图所示(见样例):
他们二人从图上的起点出发,为了更快的打败魔王,他只能向右或者向下前进,直到到达下方的魔王城。在走过的路上,他们能够获得地图里的数字(获取后地图中将变为数字0)变为法力值。小明先从起点出发走到魔王城,然后小红再出发到达魔王城,为了更好的对抗魔王,请你帮帮2位魔法师使其获得的法力值为最大。
输入描述:
第一行为1个整数N(0<N<=9)表示N*N的地图,然后输入若干行,每行中有3个整数,前2个表示位置坐标x,y(1<=x,y<=N),第3个数为该坐标提供的法力值k(0<=k<65536)。最后一行数输入单独的一行0 0 0表示输入结束。
输出描述:
只需要输出一个整数,表示小明和小红能获得的最大法力值的和。
样例1:
输入:
7
2 1 12
2 5 8
2 6 6
3 3 5
4 3 13
6 6 5
7 2 1
0 0 0
输出:
49
样例2:
输入:
5
1 1 5
1 3 6
2 5 6
4 2 13
5 1 5
0 0 0
输出:
30
2、解法1
贪心:进行两次动态规划,每次取最大值。这个方法无法获取全局最大,要获取全局最大请继续向下读
动态规划
- 1、确定dp数组极其含义
dp[i][j]表示坐标为i,j的位置最大法力为dp[i][j]. - 2、确定递推公式
dp[i][j]可由两个方向进行得到,一个是dp[i-1][j],另一个是dp[i][j-1],因为本题要获取最大法力,因此:dp[i][j]=max(dp[i-1][j],dp[i][j-1])+mp[i][j] - 3、如何初始化
因为只有两个方向,因此在边缘上对其进行累加即可。
完整cpp代码:
#include<iostream>
#include<vector>
#include<queue>
#include<string>
#include<bits/stdc++.h>
using namespace std;
void my_dp(int N,vector<vector<int>> &dp, vector<vector<int>>& mp, vector<vector<int>>& mp_b)
{
for (int i = 1; i < N; i++)
{
for (int j = 1; j < N; j++)
{
int a = dp[i - 1][j];
int b = dp[i][j - 1];
if (a >= b) //选择向右的路径
{
dp[i][j] = a + mp[i][j]; // 计算此时的最大法力值
mp[i - 1][j] = 0; // 由题意,我们走了这个地方就需要将该点的法力值清0
for (int p = 0; p < j; p++)// 那么该点上面的数据我们需要将其保持恒定不变,从备份图中拷贝过来
mp[i][p] = mp_b[i][p];
}
else
{
dp[i][j] = b + mp[i][j];// 和上面一样
mp[i][j - 1] = 0;
for (int p = 0; p < i; p++)
mp[p][j] = mp_b[p][j];
}
}
}
}
void my_Init(int N,vector<vector<int>> &dp, vector<vector<int>> mp)
{
int tem = 0;
for (int i = 0; i < N; i++) // 进行初始化,因为只能向下和向右,因此边缘为边缘数据的累和
{
tem += mp[i][0];
dp[i][0] = tem;
}
tem = 0;
for (int i = 0; i < N; i++)
{
tem += mp[0][i];
dp[0][i] = tem;
}
}
int main() {
int N = 0;
cin >> N;
vector<vector<int>> mp(N,vector<int>(N,0));//构建地图
while (true) // 数据输入
{
int x, y, z;
cin >> x >> y >> z;
if (x == 0 && y == 0 && z == 0)
break;
mp[x-1][y-1] = z;
}
vector<vector<int>> mp_b = mp; // 进行地图备份
// dp[i][j] 表示位置在i和j处能获得的最大法力
// dp[i][j] = max(dp[i-1][j],dp[i][j-1])
// 创建dp数组
vector<vector<int>> dp(N,vector<int>(N,0));
vector<vector<int>> dp1(N, vector<int>(N, 0));
// 进行第一个人dp数组初始化
my_Init(N,dp,mp);
//进行第一个人dp
my_dp(N,dp,mp,mp_b);
mp[0][0] = 0;
mp[N - 1][N - 1] = 0; // 上面是从1,1点开始的,我们需要手动将起点和重点清0
// 第二轮开始,从新初始化dp1
my_Init(N, dp1, mp);
// 进行第二个人的dp
my_dp(N, dp1, mp, mp_b);
// 两人最大法力值为两个dp数组的和
cout << dp[N-1][N-1]+dp1[N-1][N-1] << endl;
}
该方法只能达到局部最优,但无法实现全局最优,如这位网友( m0_49301123
)举的一个例子:
0 0 0 0 0
0 0 5 0 0
0 13 0 0 0
0 0 0 8 0
0 1 0 0 0
两次分别取最大值会是第一次13+8=21,第二次5,总21+5=26
而实际上应该是第一次13+1=14,第二次5+8=13,总14+13=27
使用上面方法就无法进行实现。
3、解法2
使用回溯进行深搜:当第一个人选择了一条路径时,我们在这条路径的基础上在进行第二个人的路径搜索,总的来讲就是深搜在嵌套一层深搜索。但这个方法速度很慢,但能跑出正确结果。
完整cpp代码如下:
#include<iostream>
#include<vector>
#include<queue>
#include<string>
#include<bits/stdc++.h>
using namespace std;
int dx[2] = {1,0};
int dy[2] = {0,1};
int** vis = NULL;
int** vis1 = NULL;
vector<pair<int, int>> path; // 代表走过的路径的坐标
vector<pair<int, int>> min_path; //记录最小的路径
vector<pair<int, int>> path1; // 代表走过的路径的坐标
vector<pair<int, int>> min_path1; //记录最小的路径
int mi = INT_MIN;
vector<vector<int>> mp_b; // 地图备份
void dfs1(int x, int y, int dxs, int dxy, vector<vector<int>>& map, int m, int n, int step, vector<pair<int, int>> ph)
{
// 函数的出口
if (x == dxs && y == dxy)
{
// 统计这一条路径的中总和路径长度
int tem = 0;
for (int i = 0; i < path1.size(); i++)
{
tem += map[path1[i].first][path1[i].second];
}
// 统计前一条路径和
for (int i = 0; i < ph.size(); i++)
{
tem += mp_b[ph[i].first][ph[i].second];
}
// 如果这条路径比前一条还短,那么就进行路径更新
if (tem >= mi)
{
//min_path1 = path1;
mi = tem;
}
return;
}
// 进行是个方向进行探索
for (int i = 0; i < 2; i++)
{
int tx = x + dx[i];
int ty = y + dy[i];
// 判断是否越界,并且判断是否没有访问
if (tx >= 0 && ty >= 0 && tx < m && ty < n && !vis1[tx][ty]) // 注意这里tx是小于行,ty是小于列
{
// 如果没有走过,并且没有越界,进行探索
// 首先标记这个点为已经走过
vis[tx][ty] = 1;
// 并将这个点压入path中
path1.push_back(pair<int, int>(tx, ty));
dfs1(tx, ty, dxs, dxy, map, m, n, step + 1,ph);
// 回溯的时候将其取消标记
vis[tx][ty] = 0;
// 并将这个点出站
path1.pop_back();
}
}
}
void dfs(int x, int y, int dxs, int dxy, vector<vector<int>> &map, int m, int n, int step)
{
// 函数的出口
if (x == dxs && y == dxy)
{
// 进行第二个人的dfs
vis1[0][0] = 1;
// 将path中的地图清0
for (int i = 0; i < path.size(); i++)
{
map[path[i].first][path[i].second] = 0;
/*tem += map[path[i].first][path[i].second];*/
}
dfs1(0,0,m-1,n-1, map,m,n,0,path);
for (int w = 0; w < m; w++) // 进行清零 为下一次做准备
{
for (int w1 = 0; w1 < n; w1++)
{
vis1[w][w1] = 0;
}
}
// 将path中的地图恢复
for (int i = 0; i < path.size(); i++)
{
map[path[i].first][path[i].second] = mp_b[path[i].first][path[i].second];
}
统计这一条路径的中总和路径长度
//int tem = 0;
//for (int i = 0; i < path.size(); i++)
//{
// tem += map[path[i].first][path[i].second];
//}
如果这条路径比前一条还短,那么就进行路径更新
//if (tem >= mi)
//{
// min_path = path;
// mi = tem;
//}
return;
}
// 进行是个方向进行探索
for (int i = 0; i < 2; i++)
{
int tx = x + dx[i];
int ty = y + dy[i];
// 判断是否越界,并且判断是否没有访问
if (tx >= 0 && ty >= 0 && tx < m && ty < n && !vis[tx][ty]) // 注意这里tx是小于行,ty是小于列
{
// 如果没有走过,并且没有越界,进行探索
// 首先标记这个点为已经走过
vis[tx][ty] = 1;
// 并将这个点压入path中
path.push_back(pair<int, int>(tx, ty));
dfs(tx, ty, dxs, dxy, map, m, n, step + 1);
// 回溯的时候将其取消标记
vis[tx][ty] = 0;
// 并将这个点出站
path.pop_back();
}
}
}
int** to_arr(int a, int b)
{
int** map = (int**)malloc(a * sizeof(int*));
for (int i = 0; i < a; i++)
map[i] = (int*)malloc(b * sizeof(int)); // 进行内存申请
// 进行初始化
for (int i = 0; i < a; i++)
for (int j = 0; j < b; j++)
memset(map[i], 0, b * sizeof(int));
return map;
}
int main() {
int N = 0;
cin >> N;
int m = N ;
int n = N ;
vector<vector<int>> mp(N, vector<int>(N, 0));//构建地图
while (true) // 数据输入
{
int x, y, z;
cin >> x >> y >> z;
if (x == 0 && y == 0 && z == 0)
break;
mp[x - 1][y - 1] = z;
}
// 进行备份
mp_b = mp;
vis = to_arr(m, n);//申请已访问标记数组
vis1 = to_arr(m, n);//申请已访问标记数组
//将0,0标记为已访问
vis[0][0] = 1;
path.push_back(pair<int, int>(0, 0));
dfs(0, 0, m - 1, n - 1, mp, m, n, 0);
cout << mi << endl;
//dfs();
}
上面那种情况测试用例:
输入:
5
2 3 5
3 2 13
4 4 8
5 2 1
0 0 0
输出:
27
4、解法3
总体来讲,上面方法二进行递归嵌套对整个题的开销是非常大的,但是不进行递归又无法获取全局最大,为此,折中的方法是在递归的基础上进行动态规划。
这个方法的时间复杂度较方法1要慢,较方法二要快,但方法1无法获取全局最优,因此,总体上来看,这个方法是目前较好的一种解决方法。如果有同学有更好的解决方法,可以在评论区留言,大家一起学习进步
#include<iostream>
#include<vector>
#include<queue>
#include<string>
#include<bits/stdc++.h>
using namespace std;
int dx[2] = {1,0};
int dy[2] = {0,1};
int** vis = NULL;
int** vis1 = NULL;
vector<pair<int, int>> path; // 代表走过的路径的坐标
vector<pair<int, int>> min_path; //记录最小的路径
int mi = INT_MIN;
vector<vector<int>> mp_b; // 地图备份
void my_dp(int N, vector<vector<int>>& dp, vector<vector<int>>& mp)
{
for (int i = 1; i < N; i++)
{
for (int j = 1; j < N; j++)
{
int a = dp[i - 1][j];
int b = dp[i][j - 1];
if (a >= b) //选择向右的路径
{
dp[i][j] = a + mp[i][j]; // 计算此时的最大法力值
}
else
{
dp[i][j] = b + mp[i][j];// 和上面一样
}
}
}
}
void my_Init(int N, vector<vector<int>>& dp, vector<vector<int>> mp)
{
int tem = 0;
for (int i = 0; i < N; i++) // 进行初始化,因为只能向下和向右,因此边缘为边缘数据的累和
{
tem += mp[i][0];
dp[i][0] = tem;
}
tem = 0;
for (int i = 0; i < N; i++)
{
tem += mp[0][i];
dp[0][i] = tem;
}
}
void dfs(int x, int y, int dxs, int dxy, vector<vector<int>> &map, int m, int n, int step)
{
// 函数的出口
if (x == dxs && y == dxy)
{
// 进行第二个人的dfs
vis1[0][0] = 1;
// 将path中的地图清0
for (int i = 0; i < path.size(); i++)
{
map[path[i].first][path[i].second] = 0;
/*tem += map[path[i].first][path[i].second];*/
}
vector<vector<int>> dp(m, vector<int>(m, 0));
my_Init(m, dp, map);
my_dp(m, dp, map);
int tem = 0;
for (int i = 0; i < path.size(); i++)
{
tem += mp_b[path[i].first][path[i].second];
}
tem = tem + dp[m - 1][m - 1];
// 如果这条路径比前一条还短,那么就进行路径更新
if (tem >= mi)
{
//min_path1 = path1;
mi = tem;
}
// 将path中的地图恢复
for (int i = 0; i < path.size(); i++)
{
map[path[i].first][path[i].second] = mp_b[path[i].first][path[i].second];
}
return;
}
// 进行是个方向进行探索
for (int i = 0; i < 2; i++)
{
int tx = x + dx[i];
int ty = y + dy[i];
// 判断是否越界,并且判断是否没有访问
if (tx >= 0 && ty >= 0 && tx < m && ty < n && !vis[tx][ty]) // 注意这里tx是小于行,ty是小于列
{
// 如果没有走过,并且没有越界,进行探索
// 首先标记这个点为已经走过
vis[tx][ty] = 1;
// 并将这个点压入path中
path.push_back(pair<int, int>(tx, ty));
dfs(tx, ty, dxs, dxy, map, m, n, step + 1);
// 回溯的时候将其取消标记
vis[tx][ty] = 0;
// 并将这个点出站
path.pop_back();
}
}
}
int** to_arr(int a, int b)
{
int** map = (int**)malloc(a * sizeof(int*));
for (int i = 0; i < a; i++)
map[i] = (int*)malloc(b * sizeof(int)); // 进行内存申请
// 进行初始化
for (int i = 0; i < a; i++)
for (int j = 0; j < b; j++)
memset(map[i], 0, b * sizeof(int));
return map;
}
int main() {
int N = 0;
cin >> N;
int m = N ;
int n = N ;
vector<vector<int>> mp(N, vector<int>(N, 0));//构建地图
while (true) // 数据输入
{
int x, y, z;
cin >> x >> y >> z;
if (x == 0 && y == 0 && z == 0)
break;
mp[x - 1][y - 1] = z;
}
// 进行备份
mp_b = mp;
vis = to_arr(m, n);//申请已访问标记数组
vis1 = to_arr(m, n);//申请已访问标记数组
//将0,0标记为已访问
vis[0][0] = 1;
path.push_back(pair<int, int>(0, 0));
dfs(0, 0, m - 1, n - 1, mp, m, n, 0);
cout << mi << endl;
}