人生中的第一道绿题,发篇题解记录一下
DP+DFS+模拟
题意:走到了与上一个位置相同的颜色不花费金币
走到了与上一个位置不同的颜色花费一金币
走到了一个无色且上一个位置不是无色的花费二金币
走了了一个无色而上一个位置是无色的直接返回
本题类似一种模拟+dp+dfs的思想,由于我比较菜所以只用dfs去做本题,当然也可以用dfs去做。
dfs的做法主要如下:
不断深度搜索,直到到达终点,然后用min比较得出最后的最小花费金币。
刚开始我想的就是:每一步到达一个点,就判断从上一个点到达当前点所要消费的金币,先加上,然后dfs结尾再减去,达到回溯的思想,这样子做在(1,1)点的时候并不会加上,因为在main中我调用时的实参是(1,1)点的颜色,这样子到(1,1)点时,金币是+0的
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int m, n;
int s[105][105];
int visit[105][105];
int cnt = 0;
int flagx = 0;
int a1[] = { -1,0,0,1 };
int a2[] = { 0,-1,1,0 };
int ans = 1000000;
//同色的不花金币 异色的花1金币 走到无色的花2,连续走两次无色的直接return
int jiadian(int x, int y, int last)
{
if (s[x][y] == -1)
return 2;
if (s[x][y] == last)
return 0;
if (s[x][y] != last)
return 1;
}
void dfs(int x, int y,int flag, int last)
{//flag ==1 表示上一步 用过魔法 flag ==0 表示上一部没用过魔法
int t = jiadian(x, y, last);
if (x == m && y == m) //到达终点
{
cnt += t;
ans = min(ans, cnt);
flagx = 1;
cnt -= t;
return;
}
if (flag == 1 && s[x][y] == -1)
return;
visit[x][y] = 1;
if (t==2) flag = 1,last=last; //此时这个是无色,flag=1,传入的last等于他来时候的颜色
else flag = 0,last=s[x][y]; //不是无色,flag=0,last等于它本身
cnt += t;
for (int i = 0;i < 4;i++)
{
int xx = x + a1[i], yy = y + a2[i];
if (xx<1 || yy<1 || xx>m || yy>m || visit[xx][yy])
continue;
dfs(xx, yy, flag, last);
}
visit[x][y] = 0;
cnt -= t;
}
int main()
{
memset(s, -1, sizeof(s));
//-1 无色 0 红色 1 黄色
int v1, v2,color;
cin >> m>>n;
for (int i = 0;i < n;i++)
{
cin >> v1 >> v2>>color;
s[v1][v2] = color;
}
visit[1][1] = 1;
dfs(1,1,0,s[1][1]);
if (flagx) //到达了
cout << ans;
else
cout << -1;
}
这样子可以得到一部分正确答案,但是TLE了,因为我并没有剪枝,在棋盘过大的时候去采用回溯算法会导致很多重复的步骤,dfs剪枝就是要去删除掉一些重复的步骤
我们根据dp(动态规划)的思想知道,dp是一种记忆化搜索思想,我们先记录当前到达这个点的最小花费金币,如果下一次访问这个点的时候,他花费的金币>=当前存储的最小花费金币,就可以直接返回避免多余的步骤了。而如果<当前存储的最小花费金币,就更新数组值
下面贴上ac代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int m, n;
int s[105][105];
int visit[105][105];
int cnt = 0;
int flagx = 0;
int a1[] = { -1,0,0,1 };
int a2[] = { 0,-1,1,0 };
int dp[105][105];
int ans = 1000000;
//同色的不花金币 异色的花1金币 走到无色的花2,连续走两次无色的直接return
int jiadian(int x, int y, int last)
{
if (s[x][y] == -1)
return 2;
if (s[x][y] == last)
return 0;
if (s[x][y] != last)
return 1;
}//last表示上一个点的颜色,flag表示上一个点是否使用过魔法
void dfs(int x, int y,int flag, int last)
{//flag ==1 表示上一步 用过魔法 flag ==0 表示上一部没用过魔法
if (dp[x][y] <= cnt) //如果当前数组值比已记录的花费金币小,直接返回
return;
dp[x][y] = cnt; //如果当前数组值比已记录的花费金币大,更新数组值
int t = jiadian(x, y, last);
if (x == m && y == m) //到达终点
{
cnt += t;
ans = min(ans, cnt);
flagx = 1; //能到达
cnt -= t; //回溯
return;
}
if (flag == 1 && s[x][y] == -1) //如果连续进入两次无色,直接返回
return;
visit[x][y] = 1;
if (t==2) flag = 1,last=last; //此时这个是无色,flag=1,传入的last等于他来时候的颜色
else flag = 0,last=s[x][y]; //不是无色,flag=0,last等于它本身
cnt += t; //加上当前点的花费金币
for (int i = 0;i < 4;i++)
{
int xx = x + a1[i], yy = y + a2[i];
if (xx<1 || yy<1 || xx>m || yy>m || visit[xx][yy])
continue;
dfs(xx, yy, flag, last);
}
visit[x][y] = 0;
cnt -= t; //回溯
}
int main()
{
memset(s, -1, sizeof(s)); //颜色数组初始化为无色
memset(dp, 0x3f3f3f, sizeof(dp)); //dp数组初始化为最大值
//-1 无色 0 红色 1 黄色
int v1, v2,color;
cin >> m>>n;
for (int i = 0;i < n;i++)
{
scanf_s("%d%d%d", &v1, &v2,&color);
s[v1][v2] = color;
}
visit[1][1] = 1;
dfs(1,1,0,s[1][1]);
if (flagx) //到达了
cout << ans;
else
cout << -1;
}