DFS的例题简析——from vj(连连看和black and white)

题目:连连看

“连连看”相信很多人都玩过。没玩过也没关系,下面我给大家介绍一下游戏规则:在一个棋盘中,放了很多的棋子。如果某两个相同的棋子,可以通过一条线连起来(这条线不能经过其它棋子),而且线的转折次数不超过两次,那么这两个棋子就可以在棋盘上消去。不好意思,由于我以前没有玩过连连看,咨询了同学的意见,连线不能从外面绕过去的,但事实上这是错的。现在已经酿成大祸,就只能将错就错了,连线不能从外围绕过。
玩家鼠标先后点击两块棋子,试图将他们消去,然后游戏的后台判断这两个方格能不能消去。现在你的任务就是写这个后台程序。

Input
输入数据有多组。每组数据的第一行有两个正整数n,m(0<n<=1000,0<m<1000),分别表示棋盘的行数与列数。在接下来的n行中,每行有m个非负整数描述棋盘的方格分布。0表示这个位置没有棋子,正整数表示棋子的类型。接下来的一行是一个正整数q(0<q<50),表示下面有q次询问。在接下来的q行里,每行有四个正整数x1,y1,x2,y2,表示询问第x1行y1列的棋子与第x2行y2列的棋子能不能消去。n=0,m=0时,输入结束。
注意:询问之间无先后关系,都是针对当前状态的!

Output
每一组输入数据对应一行输出。如果能消去则输出"YES",不能则输出"NO"。

Sample Input
3 4
1 2 3 4
0 0 0 0
4 3 2 1
4
1 1 3 4
1 1 2 4
1 1 3 3
2 1 2 4
3 4
0 1 4 3
0 2 4 1
0 0 0 0
2
1 1 2 4
1 3 2 3
0 0

Sample Output
YES
NO
NO
NO
NO
YES

#include <iostream>
#include <cstring>
#include <algorithm>
 using namespace std;
 const int N=1001;
 int n,m,sign;//sign作为如何输出的标志,为0输出no,为1输出yes 
 int a[N][N];
 int dir[4][2]={{-1,0},{1,0},{0,1},{0,-1}};
 bool check(int nowx,int nowy,int pos,int endx,int endy)
 {//nowx代表现在的横坐标,nowy代表现在的纵坐标,endx和endy代表最终的坐标 
 	int x=endx-nowx; //x和y记录差值 
 	int y=endy-nowy;
 	if(x!=0) x=x/abs(x);//不为0的话,可以除以自身的绝对值得到正1或者负1,也就是代表着走的方向 
 	if(y!=0) y=y/abs(y);
 	if(x==dir[pos][0]&&y==dir[pos][1])//如果跟当前位置的方向一致,说明连线到目前为止没有拐点 
	 return true;
 	else
 	 return false;
 }
 void dfs(int nowx,int nowy,int step,int pos,int endx,int endy)//step记录转折次数,pos记录方向 
 {
 	if(sign) return; 
 	if(nowx>n||nowx<1||nowy>m||nowy<1||step>2) 
 	  return;
 	if(nowx==endx&&nowy==endy)
 	{
 		sign=1;
 		return;
	 }
	if(step==2&&!check(nowx,nowy,pos,endx,endy))//当转折了两次以后,check的值还是为0,也就是又出现了拐点,不合题意,return 
	{
		return;
	}
	if(a[nowx][nowy]!=0) 
	 return;
	for(int i=0;i<4;i++)
	{
		int nextx=nowx+dir[i][0];
		int nexty=nowy+dir[i][1];
		if(i==pos)//方向一致,没有拐点 
		 dfs(nextx,nexty,step,pos,endx,endy);
		else
		 dfs(nextx,nexty,step+1,i,endx,endy);//拐点加一 
	}
 }
 int main()
 {
 	while(scanf("%d %d",&n,&m)!=EOF&&(n||m))//这里是说n,m不能为0 
 	{
 		memset(a,0,sizeof(a));
 		for(int i=1;i<=n;i++)
		 {
		 	for(int j=1;j<=m;j++)
		 	{
		 		scanf("%d",&a[i][j]);
			 }
		  }
		int b;
		scanf("%d",&b);
		while(b--)
		{
			sign=0;
			int x0,y0,x2,y2;
			scanf("%d %d %d %d",&x0,&y0,&x2,&y2);//不用y1是因为y1在math.h里定义过,是函数 
			if(a[x0][y0]==0||a[x0][y0]!=a[x2][y2])//排除前后为空和不相等的情况 
			{
				printf("NO\n");
				continue;
			}
		    for(int i=0;i<4;i++)
		   {
			 int dx=x0+dir[i][0];
			 int dy=y0+dir[i][1];
			 dfs(dx,dy,0,i,x2,y2);//刚开始拐点数记为0 
		   } 
		   if(sign)
		    printf("YES\n");
		    else
		    printf("NO\n");
	 }
  }
  return 0; 
}

这是dfs的做法,实际上还可以用bfs的做法做出来。请读者盆友们自行思考哦~

题目-Black And White

In mathematics, the four color theorem, or the four color map theorem, states that, given any separation of a plane into contiguous regions, producing a figure called a map, no more than four colors are required to color the regions of the map so that no two adjacent regions have the same color.
— Wikipedia, the free encyclopedia

In this problem, you have to solve the 4-color problem. Hey, I’m just joking.

You are asked to solve a similar problem:

Color an N × M chessboard with K colors numbered from 1 to K such that no two adjacent cells have the same color (two cells are adjacent if they share an edge). The i-th color should be used in exactly ci cells.

Matt hopes you can tell him a possible coloring.
Input
The first line contains only one integer T (1 ≤ T ≤ 5000), which indicates the number of test cases.

For each test case, the first line contains three integers: N, M, K (0 < N, M ≤ 5, 0 < K ≤ N × M ).

The second line contains K integers ci (ci > 0), denoting the number of cells where the i-th color should be used.

It’s guaranteed that c1 + c2 + · · · + cK = N × M .
Output
For each test case, the first line contains “Case #x:”, where x is the case number (starting from 1).

In the second line, output “NO” if there is no coloring satisfying the requirements. Otherwise, output “YES” in one line. Each of the following N lines contains M numbers seperated by single whitespace, denoting the color of the cells.

If there are multiple solutions, output any of them.

Sample Input
4
1 5 2
4 1
3 3 4
1 2 2 4
2 3 3
2 2 2
3 2 3
2 2 2

Sample Output
Case #1:
NO
Case #2:
YES
4 3 4
2 1 2
4 3 4
Case #3:
YES
1 2 3
2 3 1
Case #4:
YES
1 2
2 3
3 1

思路:
从左上角搜索到右下角,只需要判断是否和上面左面的砖块颜色是否相同。
理解一下,按顺序放进去,就只要判断左边和上面啦啦啦
思路
当剩下的砖块数量+1小于某种颜色的数量的两倍,这时必然会相邻
比如说一开始的时候,n=m=3,然后红色的有6个,在放完5个以后,无论放哪里都会有相邻情况,如下图:

在这里插入图片描述
而在你遍历完一行以后,第一行的三个颜色可以看成是已经填进去了的。此时,假设有种颜色是5个,那么放完四个以后,无论在哪放,也会有相邻的情况。所以这个时候也就是 dfs(x,y,sum-1)
在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>
 using namespace std;
 const int N=26;
 int a[N],sign,vis[N][N],k,n,m,num;
 bool check(int x,int y,int col)//第x行,第y列,填充的颜色为col 
 {//先把第一列,第一行的特殊情况单独考虑 
 	if(x==1&&y==1)//第一行第一列的格子 
 	{
 		return 1;
	 }
	if(x==1)
	{
		if(vis[x][y-1]!=col)//相邻的上面的格子的颜色与检查的格子的颜色是否相等 
		{
			return 1;
		}
		else 
		 return 0;
	}
	if(y==1)
	{
	  if(vis[x-1][y]!=col)//相邻的左边的格子的检查 
	  {
	  	return 1;
	  }
	  else
	   return 0;
	 } 
	 if(vis[x-1][y]!=col&&vis[x][y-1]!=col)
	 {
	 	return 1;
	 }
	 else
	  return 0;
	 
 }
 void Dfs(int x,int y,int sum)//第x行,第y列,sum个格子 
 {
 	if(sign)
 	 return;
 	for(int i=1;i<=k;i++)
 	{
 		if((sum+1)/2<a[i])
 		  return;
	 }
	for(int i=1;i<=k;i++)
	{
	  if(check(x,y,i)&&a[i]>0)
	  {
	  	vis[x][y]=i;//格子用对应的颜色标记好 
	  	a[i]--;//涂好一个少一个 
	  	if(x==n&&y==m&&sign==0)//遍历到了最后一行的最后一个格子 
	  	{
	  		sign=1;
	  		printf("Case #%d:\nYES\n",num); 
	  		for(int j=1;j<=n;j++)
	  		{
	  			cout<<vis[j][1];//回溯 
	  			for(int jj=2;jj<=m;jj++)
	  			{
	  				cout<<' '<<vis[j][jj];
				  }
				 cout<<endl; 
			  }
		  }
		else if(y==m)//列到了最后一列 
		{
			Dfs(x+1,1,sum-1);//行加一,总数减一(如果不明白再看看思路哦) 
		}
		else{
			Dfs(x,y+1,sum-1);
		}
		//否则,恢复原状 
		vis[x][y]=0;
		a[i]++;
	  }	
	}
 }
 int main()
 {
 	int T;
 	scanf("%d",&T);
 	memset(vis,0,sizeof(vis));
 	for(int i=1;i<=T;i++)
 	{
 		sign=0;
 		num++;
 		scanf("%d %d %d",&n,&m,&k);
 		memset(a,0,sizeof(a));
 		for(int i=1;i<=k;i++)
 		{
 			scanf("%d",&a[i]);
		}
		Dfs(1,1,n*m);
		if(sign==0)
		printf("Case #%d:\nNO\n",num);
	 }
 	return 0;
  } 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值