深度搜索dfs感悟

深搜的基本框架:

void dos(int step)

{

   判断边界

  尝试每一种可能for

    继续下一步 dfs(step+1)

  }

返回

在做了几道比较基础的深搜的题,对深搜有了点感悟,现在已经遇到了两种类型,一个是类似于全排列的题,如六角填数,或基本的全排列输出,用六角填数来举个例子,这个思想就像是有12个格子,手中有12个数字,把数字放入格子满足某种条件,这类题深搜思想的终止条件就是stpe==最后一个格子+1,其余的就用visit,dfs和回溯,还有一种题就是表格题,这种题就要具体分析了,对于n皇后问题,那么我们就定位行列,对于炸弹人我们就定位格子数,而对于石油合并和red and black 总是找不到终止条件,这个一直很迷糊,感觉其他的n皇后,或者是炸弹人,对于他们的行和列,放不放,如果放放几个,都是不确定的,所以每一个格子都有机会放,或者不放,而对于石油合并和red and black他们都不是放东西,而是找东西,找个数,于是他们只要找出满足的个数就可以了,并不需要从第一个格子到最后一个格子全部的去dfs



在每一次的搜索标记的时候,应注意使每一个例子的标记都出实话,如全局变量book[25][25],记得在主函数调用c++的csting  memset(book,0,sizeof(book))使得每一个例子最开始都未被标记

1045题

Suppose that we have a square city with straight streets. A map of a city is a square board with n rows and n columns, each representing a street or a piece of wall. 

A blockhouse is a small castle that has four openings through which to shoot. The four openings are facing North, East, South, and West, respectively. There will be one machine gun shooting through each opening. 

Here we assume that a bullet is so powerful that it can run across any distance and destroy a blockhouse on its way. On the other hand, a wall is so strongly built that can stop the bullets. 

The goal is to place as many blockhouses in a city as possible so that no two can destroy each other. A configuration of blockhouses is legal provided that no two blockhouses are on the same horizontal row or vertical column in a map unless there is at least one wall separating them. In this problem we will consider small square cities (at most 4x4) that contain walls through which bullets cannot run through. 

The following image shows five pictures of the same board. The first picture is the empty board, the second and third pictures show legal configurations, and the fourth and fifth pictures show illegal configurations. For this board, the maximum number of blockhouses in a legal configuration is 5; the second picture shows one way to do it, but there are several other ways. 



Your task is to write a program that, given a description of a map, calculates the maximum number of blockhouses that can be placed in the city in a legal configuration. 
 

Input
The input file contains one or more map descriptions, followed by a line containing the number 0 that signals the end of the file. Each map description begins with a line containing a positive integer n that is the size of the city; n will be at most 4. The next n lines each describe one row of the map, with a '.' indicating an open space and an uppercase 'X' indicating a wall. There are no spaces in the input file. 
 

Output
For each test case, output one line containing the maximum number of blockhouses that can be placed in the city in a legal configuration.
 

Sample Input
  
  
4 .X.. .... XX.. .... 2 XX .X 3 .X. X.X .X. 3 ... .XX .XX 4 .... .... .... .... 0
 

Sample Output
  
  
5 1 5 2 4

<span style="font-size:14px;">#include <iostream>
using namespace std;
char a[5][5];
int max1=0,n;
int check(int x,int y)
{
    int i;
    for(i=y+1;i<=n&&a[x][i]!='X';i++)
    {
        if(a[x][i]=='@')
            return 0;
    }
    for(i=y-1;i>0&&a[x][i]!='X';i--)
    {
        if(a[x][i]=='@')
            return 0;
    }
    for(i=x+1;i<=n&&a[i][y]!='X';i++)
    {
        if(a[i][y]=='@')
            return 0;
    }
    for(i=x-1;i>0&&a[i][y]!='X';i--)
    {
        if(a[i][y]=='@')
            return 0;
    }
    return 1;
}
void dfs(int num,int boom)
{
    if(num==n*n)
    {
        if(boom>max1)
            max1=boom;
        return;
    }
   <span style="color:#ff0000;"> int x;int y;
    x=num/n+1;
    y=num%n+1;
    if(a[x][y]=='.')
    {
        if(check(x,y)==1)
        {
            a[x][y]='@';
            dfs(num+1,boom+1);
            a[x][y]='.';
        }
    }
    dfs(num+1,boom);
    </span>
}
int main()
{
    int i,j;
    while(cin>>n)
    {
        if(n==0)
            break;
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=n;j++)
        {
            cin>>a[i][j];
        }
    }
    max1=0;
    dfs(0,0);
    cout<<max1<<endl;
    }
}</span>
<span style="font-size:14px;">#include <iostream>
using namespace std;
int a[51][51],book[51][51]={0};
int n,m,tx,ty,q,p,k,mia=99999999;
void dfs(int x,int y,int step)
{
    int next[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
    if(x==p && y==q)
    {
        if(step<mia)
            mia=step;
        return ;
    }
    <span style="color:#ff0000;">for(k=0;k<=3;k++)
    {
        tx=x+next[k][0];
        ty=y+next[k][1];
        if(tx<1 || tx>n || ty<1 ||ty>m)
        continue;
        if(a[tx][ty]==0 && book[tx][ty]==0)
        {
            book[tx][ty]=1;
            dfs(tx,ty,step+1);
            book[tx][ty]=0;
        }
    }
}</span>
int main()
{
    int i,j,x1,y1;
    cin>>n>>m;
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=m;j++)
        {
            cin>>a[i][j];
        }
    }
    cin>>x1>>y1>>p>>q;
    book[x1][y1]=1;
    dfs(x1,y1,0);
    cout<<mia;
}救啊哈
</span>
这两个红色代码就是尝试每一种可能,第一段红色代码通过假设在每一个红色格子上先放上炸弹,再通过num的数量来依次往从第一个放炸弹的地方往后依次尝试放炸弹,例如刚开始num=0,则表示从1,1的位置放炸弹,然后递归可以求出如果第一个炸弹在1,1这个位置,最多可以放多少个炸弹,然后num=1,表示1,2这个位置最开始放炸弹,依次类推,

There is a rectangular room, covered with square tiles. Each tile is colored either red or black. A man is standing on a black tile. From a tile, he can move to one of four adjacent tiles. But he can't move on red tiles, he can move only on black tiles.

Write a program to count the number of black tiles which he can reach by repeating the moves described above. 
 

Input
The input consists of multiple data sets. A data set starts with a line containing two positive integers W and H; W and H are the numbers of tiles in the x- and y- directions, respectively. W and H are not more than 20.

There are H more lines in the data set, each of which includes W characters. Each character represents the color of a tile as follows.

'.' - a black tile 
'#' - a red tile 
'@' - a man on a black tile(appears exactly once in a data set) 
 

Output
For each data set, your program should output a line which contains the number of tiles he can reach from the initial tile (including itself). 
 

Sample Input
  
  
6 9 ....#. .....# ...... ...... ...... ...... ...... #@...# .#..#. 11 9 .#......... .#.#######. .#.#.....#. .#.#.###.#. .#.#..@#.#. .#.#####.#. .#.......#. .#########. ........... 11 6 ..#..#..#.. ..#..#..#.. ..#..#..### ..#..#..#@. ..#..#..#.. ..#..#..#.. 7 7 ..#.#.. ..#.#.. ###.### ...@... ###.### ..#.#.. ..#.#.. 0 0
#include<stdio.h>
#include <cstring>
char s[25][25];
int a[4]={0,0,1,-1};
int b[4]={1,-1,0,0};
int book[25][25]={0};
int m,n,sum;
void bfs(int x,int y)
{
    int k,v,t;
    for(k=0;k<4;k++)
    {
        v=x+a[k];
        t=y+b[k];
        if(s[v][t]=='.'&&v>=1&&v<=n&&t>=0&&t<m && book[v][t]==0)
        {
            sum++;
            book[v][t]=1;
            bfs(v,t);
        }
    }
}
int main()
{
    int i,j;
    while(scanf("%d%d",&m,&n)&&(m+n))
    {
        memset(book,0,sizeof(book));
        for(i=1;i<=n;i++)
            scanf("%s",s[i]);
        for(i=1,sum=1;i<=n;i++)
        {
            for(j=0;j<m;j++)
            {
                if(s[i][j]=='@')
                {
                    book[i][j]=1;
                    bfs(i,j);
                }
            }
        }
        printf("%d\n",sum);
    }
    return 0;
}

这个题不用回溯和返回,为什么呢,我觉的假如从@的左边开始上下左右的搜索和标记,肯定会有一些满足条件的点没有被标记,在从@的上边开始上下左右的标记,再从下边,再从右边,感觉其实限制它的条件就是#和边界,只要不是他们两个,就上下左右的搜索和标记,等到递归完成后,就求出了值

在N*N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上。
你的任务是,对于给定的N,求出有多少种合法的放置方法。

 

Input
共有若干行,每行一个正整数N≤10,表示棋盘和皇后的数量;如果N=0,表示结束。
 

Output
共有若干行,每行一个正整数,表示对应输入行的皇后的不同放置数量。
 

Sample Input
  
  
1 8 5 0
 

Sample Output
  
  
1 92 10
#include<stdio.h>
#include<math.h>
int x[15];//x数组表示放在该列的哪个位置,下标表示列,值表示放置在第几行 
int sum, n;
bool place (int v)
{
	int i;
	for(i=1;i<v;++i)
	{
		if(x[i] == x[v] || abs(x[i]-x[v])==abs(i-v))//如果在同一行,或在对角线方向(为什么这里不判断是否为同一列呢?主要是因为x数组表示放在该列的哪个位置) 
			return 0;
	}
	return 1;
}
void backtrack(int v)
{
	int i;
	if(v>n) //把最后一个皇后放置成功后 
		sum++;
	else
	{
		for(i=1;i<=n;++i) //找n个皇后 
		{
			x[v]=i;
			if(place(v))
			{
				backtrack(v+1);
			}
		}
	}
}
int main()
{	
	int ans[15];
	for(n = 1; n <= 10; ++n)
	{
		sum = 0;
		backtrack(1);
		ans[n] = sum;	
	}	
	while(scanf("%d",&n), n)
	{
		printf("%d\n",ans[n]);
	}
	return 0;
}

这道题的思想是假如有一个n*n的格子,在满足题意的情况下,每一行和每一列只能放一个皇后,所以该题就是先限制列然后判断行,如果该行该列不能放,就变成下一行的该列,知道所有列都被搜索万即v等于n+1

某石油公司发现了一个油田。该油田由n*m个单元组成的矩形,有些单元里有石油,有些则没有。单元油田可以通过上,下,左或右连通。在一个单元油田里架设一台采油机,它可以把和该单元油田相连的单元油田的石油采完。该公司想知道最少需要架设几台采油机能把所有的石油采完?

Input

先输入2个正整数n,m(1<=n,m<=50)。接着有n行,每行有m个字符。'@'表示该单元有石油,'*'则表示该单元没有石油。
输入到文件结束。 

Output

对于每组测试,输出最少需要架设几台采油机。

Sample Input

2 2
@*
*@
2 2
@@
@@

Sample Output

2
1


<span style="font-size:14px;">#include <iostream>
#include <cstdio>
#include <cstring>
#define M 55
using namespace std;
char s[M][M];
int n, m, many;
int map[M][M];
int dfs(int i, int j){
    map[i][j] = 1;
    if(i+1<n&&s[i+1][j]=='@'&&map[i+1][j]==0){
        dfs(i+1,j);
    }
    if(i-1>=0&&s[i-1][j]=='@'&&map[i-1][j]==0){
        dfs(i-1,j);
    }
    if(j-1>=0&&s[i][j-1]=='@'&&map[i][j-1]==0){
        dfs(i,j-1);
    }
    if(j+1<m&&s[i][j+1]=='@'&&map[i][j+1]==0){
        dfs(i,j+1);
    }
    return 1;
}

int main()
{
    while(scanf("%d%d", &n, &m)!=EOF){
        many = 0;
        memset(map, 0, sizeof(map));
        for(int i = 0; i < n; i++){
            scanf("%s", s[i]);
        }
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(map[i][j] == 0 && s[i][j] == '@'){
                    many += dfs(i,j);
                }
            }
        }
        printf("%d\n", many);
    }
    return 0;
}

http://acm.hrbeu.edu.cn/index.php?act=problem&id=1001&cid=47</span>
这道题虽然很水,但是让我看见一个新的搜索代码方式,于是对于这个思想稍稍一改便是red and black的另一种ac代码就是优点慢

#include <iostream>
#include <cstring>
using namespace std;
char map[55][55];
int  visit[55][55];
int n,m,sum;
void dfs(int i,int j)
{
    visit[i][j]=1;
    if(i+1<=n && map[i+1][j]=='.' && visit[i+1][j]==0)
    {
        sum++;
        dfs(i+1,j);
    }
    if(i-1>=0 && map[i-1][j]=='.' && visit[i-1][j]==0)
    {
        sum++;
        dfs(i-1,j);
    }
    if(j+1<=m && map[i][j+1]=='.' && visit[i][j+1]==0)
    {
        sum++;
        dfs(i,j+1);
    }
    if(j-1>=0 && map[i][j-1]=='.' && visit[i][j-1]==0)
    {
        sum++;
        dfs(i,j-1);
    }
    return ;
}
int main()
{
    int i,j = 0,k=0;
    while(cin>>m>>n)
    {
        if(n==0 && m==0)
            break;
        sum=0;
memset(visit,0,sizeof(visit));
        memset(map,0,sizeof(map));
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=m;j++)
            {
                cin>>map[i][j];
            }
        }
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=m;j++)
            {
                if(map[i][j]=='@')
                {
                    k=1;
                    break;
                }
            }
            if(k==1)
                break;
        }
        k=0;
        sum++;
        dfs(i,j);
        cout<<sum<<endl;
    }
}



全排列问题

第二段红代码则是通过逆时针顺序进行依次试探,如果走过则坐上标记在book,两个代码都别忘了回溯

数字的全排列

<span style="font-size:14px;">#include <iostream>
using namespace std;
int a[10],c[5];
int book[10],n=4;
void dfs(int step)
{
    int i;//注意这个i必须是局部变量不能是全局变量
    if(step==n+1)
    {
        for(i=1;i<=n;i++)
            cout<<a[i]<<' ';
        cout<<endl;
        return ;
    }
    for(i=1;i<=n;i++)
    {
        if(book[i]==0)
        {
            a[step]=c[i];
            book[i]=1;
            dfs(step+1);
            book[i]=0;
        }
    }
}
int main()
{
    int j;
    for(j=1;j<=4;j++)
        cin>>c[j];
    dfs(1);
    
}</span>

为什么i一定要是局部变量而不能是全局变量呢?我的理解是如果设为全局变量在输出完一次后,i的值为n+1而return之后,对于for循环i不满足小于等于n,所以跳出了循环,而如果设成局部变量,相当于对于每一个dfs都对应一个i,每次的i都是不同的

第7题:六角填数(12')


    如图所示六角形中,填入1~12的数字。
    使得每条直线上的数字之和都相同。
    图中,已经替你填好了3个数字,请你计算星号位置所代表的数字是多少?

请通过浏览器提交答案,不要填写多余的内容。


快速解题思路:dfs 从上到下,从左到右从1到12标记每一个点。初始化1,8,3. 要求的地方点的标记为6.碰到1,2,12,就直接dfs下一个。

就类似于一个全排列,但是注意下到第1,2,12,stept的时候直接下一个,可惜自己太水,还是没有想到这点,想的太简单

#include <iostream>
using namespace std;
int x[15],book[15];
void dfs(int step)
{
    int i,k;
    if(step == 1 || step == 2 || step == 12){
        dfs(step+1);
        return ;
    }
    if(step==13)
    {
        int b[6];
            b[0]=x[1]+x[3]+x[6]+x[8];
            b[1]=x[1]+x[4]+x[7]+x[11];
            b[2]=x[2]+x[3]+x[4]+x[5];
            b[3]=x[8]+x[9]+x[10]+x[11];
            b[4]=x[2]+x[6]+x[9]+x[12];
            b[5]=x[5]+x[7]+x[10]+x[12];
            for(k=1;k<6;k++)
            {
             if(b[k-1]!=b[k])
                 return;
            }
            cout<<x[6];
            return;
}
        for(i=1;i<=12;i++)
        {
            if(book[i]==0)
            {
                x[step]=i;
                book[i]=1;
                dfs(step+1);
                book[i]=0;
            }
        }
}
int main()
{
   x[1]=1;x[2]=8;x[12]=3;
    book[1]=1;book[8]=1;book[3]=1;
    dfs(1);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值