P3956 [NOIP2017 普及组] 棋盘

人生中的第一道绿题,发篇题解记录一下

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;
}

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值