DFS专题

来源:互联网 发布:giftbox是什么软件 编辑:程序博客网 时间:2024/05/29 15:25
D - 连连看
Time Limit:10000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u
Submit Status Practice HDU 1175

Description

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

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 41 2 3 40 0 0 04 3 2 141 1 3 41 1 2 41 1 3 32 1 2 43 40 1 4 30 2 4 10 0 0 021 1 2 41 3 2 30 0

Sample Output

YESNONONONOYES


吐槽一下出题人文字表达能力,已在原文做修改。


代码:

#include<stdio.h>#include<string.h>#include<math.h>#include<stdlib.h>#include<vector>#include<queue>#include<stack>#include<iostream>#include<string>#include<algorithm>using namespace std;#define MaxSize 1005#define inf 0x3f3f3f3fint n,m,flag;int mp[MaxSize][MaxSize],book[MaxSize][MaxSize];int d[4][2]= {0,1,1,0,0,-1,-1,0};//右,下,左,上void dfs(int x, int y, int cnt, int dir,int tx, int ty){  if(x==tx&&y==ty)    {      flag=1;      return;    }  if(x<0 || x>=n || y<0 ||y>=m||book[x][y]==1||mp[x][y]!=0) return;///////////////////开始剪枝///////////////  if(cnt>2) return;  /*我最开始想的是,能走到这一步,说明前面说明没找到目标点,  如果此时的转弯次数大于等于2,再怎么走也不能在转弯2次内到目标点了。  才怪咧!这又不是step,如果当前已经转弯两次了,之后一直按这最后一次的方向走,走到目标点也是可以的呀*/  if(cnt==2)//如果已经转两次弯了,那么目标点必定只能在当前点方向的前方。    {      if(dir == 0)        {          if(!(x == tx && ty > y)) return;        }      else if(dir == 2)        {          if(!(x == tx && ty < y)) return;        }      else if(dir == 1)        {          if(!(y == ty && tx > x)) return;        }      else if(dir == 3)        {          if(!(y == ty && tx < x)) return;        }    }///////////////////剪枝结束///////////////  book[x][y]=1;  for(int i=0; i<4; i++)    {      if(dir==i)        dfs(x+d[i][0],y+d[i][1],cnt,dir,tx,ty);      else        dfs(x+d[i][0],y+d[i][1],cnt+1,i,tx,ty);    }  book[x][y]=0;}int main(){  int x,y,tx,ty,q;  while(~scanf("%d%d",&n,&m))    {      if(n==0&&m==0) break;      for(int i = 0; i < n; i++)        {          for(int j = 0; j < m; j++)            {              scanf("%d",&mp[i][j]);            }        }      scanf("%d",&q);      while(q--)        {          scanf("%d%d%d%d",&x,&y,&tx,&ty);          x--,y--,tx--,ty--;          if(mp[x][y]!=mp[tx][ty]||mp[x][y]==0||(x==tx&&y==ty)) printf("NO\n");          //注意如果是同一个点肯定是不能消去的【避坑之重复数据】          else            {              flag=0;              memset(book,0,sizeof(book));              book[x][y]=1;              for(int i=0; i<4; i++)                {                  dfs(x+d[i][0],y+d[i][1],0,i,tx,ty);                  if(flag) break;//这里也能剪枝,剪的还是大枝。我真机智。                }              if(flag) printf("YES\n");              else printf("NO\n");            }        }    }  return 0;}//FROM CJZ


这道题其实不能从外面走还要简单一点,要是能从外面走的话,就在地图外面加一圈0即可。


思考:

1、在进入dfs判断的时候,我一开始是这样写的:

 if(x<0 || x>=n || y<0 ||y>=m||book[x][y]==1||(mp[x][y]!=0&&(x!=tx&&y!=ty))) return;  if(x==tx&&y==ty)    {      flag=1;      return;    }

因为我想的是我有可能此时走到了目标点,而这个点是非0的,所以这里要留出个条件给这种情况。然而这样的话,出现下面这中情况我的代码就不对了:

5 5
1 2 1 2 1
0 0 0 2 0
0 0 0 0 0
0 0 0 0 0
1 1 1 1 1
1
1 4 1 2


因为有可能路径中存在和查询点一样的点,把路径挡住。所以没有到达最后目标点之前,我们都不能允许路径中出现非0点。所以应该改成这样:

  if(x==tx&&y==ty)    {      flag=1;      return;    }  if(x<0 || x>=n || y<0 ||y>=m||book[x][y]==1||mp[x][y]!=0) return;


2、下面我们来说说这个结构:

void dfs(int x, int y, int cnt, int dir,int tx, int ty){            .    .    .    . 判断&剪枝        .    .    .    .          book[x][y]=1;  for(int i=0; i<4; i++)    {      if(dir==i)        dfs(x+d[i][0],y+d[i][1],cnt,dir,tx,ty);      else        dfs(x+d[i][0],y+d[i][1],cnt+1,i,tx,ty);    }  book[x][y]=0;}

对于dfs而言,每一轮return之前,都出是走的一条路径。每一轮中,路径当中的所走过的每一个点都应该进行标记,来防止“贪吃蛇的自己咬到自己”的情况,即:走过的点不能重复走。

而对于这个结构而言,它先对当前点进行标记,然后对当前点进行扩展dfs。对于当前点,对由它为“起点”扩展出来的一堆堆点来说,这个当前点是被标记的,这很讲理嘛(自己想一想)。以这个点所有扩展出来的dfs都结束之后,再取消它的标记,结束以当前点为起始点的dfs路径。然后return到它的上一个点,上一个点又继续进行其他方向的dfs扩展。这种结构真是神奇,好好体会。


3、

void dfs(int x, int y, int cnt, int dir,int tx, int ty)

在bfs的时候,我们通常把点的其他属性(如:步数,时间)用结构体来存,这样在扩展的时候是没有什么问题的。而对于dfs,没有了bfs的队列节点储存结构,这些附加属性就可以写在函数的形参里面,这样扩展的时候这些属性就能照样进行步进了。






E - DFS
Time Limit:2000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u
Submit Status Practice HDU 2212

Description

A DFS(digital factorial sum) number is found by summing the factorial of every digit of a positive integer. 

For example ,consider the positive integer 145 = 1!+4!+5!, so it's a DFS number. 

Now you should find out all the DFS numbers in the range of int( [1, 2147483647] ). 

There is no input for this problem. Output all the DFS numbers in increasing order. The first 2 lines of the output are shown below.
 

Input

no input
 

Output

Output all the DFS number in increasing order.
 

Sample Output

12......

讲道理,这道题和dfs没一点关系。不过可以练习一下怎么打表。

打表代码:

#include<stdio.h>#include<string.h>#include<math.h>#include<stdlib.h>#include<vector>#include<queue>#include<stack>#include<iostream>#include<string>#include<algorithm>using namespace std;#define MaxSize 45#define inf 0x3f3f3f3fint jc[100];int main(){  jc[0]=1;  int sum;  int cnt ;  int x ;  sum=1;  for(int i = 1; i <= 9; i++)    {      sum*=i;      jc[i]=sum;    }  FILE *fp=fopen("dfs_num.txt","w+");  for(int i=1; i<=2147483647; i++)    {      sum=0;      cnt = 0;      x = i;      while(x != 0)        {          sum+=jc[x%10];          x/=10;        }      if(sum==i)      {          fprintf(fp,"%d\\n",i);          //fflush(fp);      }    }  fclose(fp);  return 0;}//FROM CJZ


这个程序在运行的过程中,我发现txt里一直没有出现数据,但是我们知道1,2都是符合要求的数据,在循环的开头的时候就应该会输出才对。去问了学长才知道,有个东西叫缓冲区,数据都在缓冲区里面。正常情况是:缓冲区接受数据->缓冲区满->打印数据,同时缓冲区清空->继续接受数据->…………因为这里的数据量很少(一共都才4个),缓冲区一直没满就不会打印结果,只有程序结束之后才会把缓冲区里的数据一起输出。如果需要实时输出数据的话可以用fflush(FILE*)函数,强行清空缓冲区,打印数据。不过对于我们来说,这里我们都要等到程序结束拿到所有数据,所以用不用都一样。


最后出来4个数据1,2,145,40585,直接打表输出就可以了。

提交代码;

#include<stdio.h>int main(){ printf("1\n2\n145\n40585\n"); return 0;}







F - 棋盘问题
Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%lld & %llu
Submit Status Practice POJ 1321

Description

在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。

Input

输入含有多组测试数据。 
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n 
当为-1 -1时表示输入结束。 
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。 

Output

对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。

Sample Input

2 1#..#4 4...#..#..#..#...-1 -1

Sample Output

21



代码:

#include<stdio.h>#include<string.h>#include<math.h>#include<stdlib.h>#include<vector>#include<queue>#include<stack>#include<iostream>#include<string>#include<algorithm>using namespace std;#define MaxSize 10#define inf 0x3f3f3f3f#define LL long long intint n,k,ans;int book[MaxSize];char mp[MaxSize][MaxSize];void dfs(int r, int cnt){  if(cnt == k)    {      ans++;      return;    }  if(r == n) return;//上面这两个判断的顺序一定要这样写*  for(int i=0; i<n; i++)    {      if( mp[r][i]=='.' || book[i]==1) continue;      book[i]=1;//说明当前i可以选,则标记。然后cnt+1,继续找下一行      /**想一想如果是在最后一行(即n-1行)找到的最后一个cnt的情况,      这就决定了上面的return顺序。如果换一个顺序的话,这种情况下ans还没++就return了,      所以不行*/      dfs(r+1,cnt+1);      book[i]=0;    }    /*并不是每一行都能找到满足条件的棋盘位置的,    如果就这样结束了的话,这次for没找到位置,没进行往后的dfs,    那么后面就断了。所以不难想到,没找到的话,我们应该有个跳行语句。    而且我们知道,在选位置的时候不一定必须一行一行挨着选,    我们可以跳过某行或者某几行(只要是满足条件的)。如果我们就只写个for就完了的话,    这明显就不能实现跳行了,所以我们让每个点多一个可以选择跳行的语句。    从以上两点来看,无论哪种情况,都应该在for结束之后有跳行的语句,即对于每一个点,    它要进行两种选择,一是在下一行里面选择合适的位置,二是跳过下一行,进入再下一行去选。    这里举个例子,比如我们要隔两行再开始选,    那么这里我们会经历dfs(r+1,cnt)进入下一行,    下一行的时候又会经历dfs((r+1)+1,cnt),所以一定会囊括所有跳行情况*/    dfs(r+1,cnt);}int main(){  while(~scanf("%d%d",&n,&k))    {      if(n==-1 && k==-1) break;      getchar();      for(int i=0; i<n; i++)        {          for(int j=0; j<n; j++)            {              scanf("%c",&mp[i][j]);            }          getchar();        }      memset(book,0,sizeof(book));      ans=0;      dfs(0,0);      printf("%d\n",ans);    }  return 0;}//FROM CJZ






1 0