DFS专题

D - 连连看
Time Limit:10000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u

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 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<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 0x3f3f3f3f

int 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

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

      
      
1 2 ......

讲道理,这道题和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 0x3f3f3f3f

int 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

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

2
1



代码:

#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 int

int 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






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值