DFS深度搜索 回溯法 HDU 2553 POJ 2386 UVA 524

HDU 2553:N皇后问题;POJ 2386:Lake Counting;UVA 524:Prime Ring Problem

学习了深度搜索 在参考了各类代码 以及自己理解后 记录几道题

棋盘类的题目一般是回溯法, 通过给定的限制条件来进行标记 再不断的dfs,当放棋子的次数大于等于要求的数量,则成功,最后再回归原状态。
相邻的算一个整体 求有几部分 这种题也是做标记 相当于是将周围的化成普通的 直到周围没有相连的 然后此时就算找到了一部分。找寻的话就不用返回原状态了。

POJ 2386:Lake Counting
一片相互连接的积水叫做“水WA”。(每个格子被认为和相邻的8个格子相连)
类似于找油田之类的

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std;
char s[1010][1010];
int n,m;
void dfs(int x,int y)
{
    s[x][y]='.';//改变符号为一般的 
    for(int dx=-1;dx<=1;dx++){//进行判断周边的 由题意就是(i,j)为中心 
    	for(int dy=-1;dy<=1;dy++){
    		int nx=x+dx,ny=y+dy;//就是周边的具体某一位置 
    		if(nx>=0&&nx<n&&ny>=0&&ny<m&&s[nx][ny]=='W'){//判断是否越界同时是否存在水洼标志 
    			dfs(nx,ny);//满足上面条件 就算有一个标志也算 再对这一个标志的周边进行判断 
			}
		}
	}
    	
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
    	for(int i=0;i<n;i++)
		{
	        for(int j=0;j<m;j++)
			{
	            cin>>s[i][j];//输入水洼图形 
	        }
    	}
    	int count=0;
	    for(int i=0;i<n;i++)
			{
		        for(int j=0;j<m;j++)
				{
		            if(s[i][j]=='W')//遇到代表水洼的符号 
		            {
		            	dfs(i,j);//进行周围的判断 
		            	count++;//由于已经进行了判断同时改变了其周边的符号
						//所以只会出现count个'#' 即可代表水洼的个数 
					}
		        }
	    	}
	    	cout<<count<<endl;
	}
    
    return 0;
}


这道题非常好理解 就是找到一个水洼 再将其变成“平地”,再找出一个再变。
下面看令我初接触深度搜索头痛的题
循序渐进吧 先看个稍微简单点的

UVA - 524 Prime Ring Problem
输入正整数n,把1—n组成一个环,是相邻的两个整数为素数。输出时从整数1开始,逆时针排列。同一个环恰好输出一次,n (0 < n <= 16)

暴力解决会超时,应用回溯法,深度优先搜索
Sample Input
6
8

Sample Output
Case 1:
1 4 3 2 5 6
1 6 5 2 3 4

Case 2:
1 2 3 8 5 6 7 4
1 2 5 8 3 4 7 6
1 4 7 6 5 8 3 2
1 6 7 4 3 8 5 2

废话不多说直接上代码

#include<iostream>
#include<string.h>
#include<math.h>
using namespace std;
int n;
int a[20],vis[20];
int isp(int n)           //判断是否为素数
{
    if(n<2)
        return 0;
    for(int i=2;i*i<=n;i++)
    {
        if(n%i==0)
        	return 0;
    }
    return 1;
}
void dfs(int s)
{
    if(s==n&&isp(a[1]+a[n]))  //递归边界 s=n代表已经用完了n个值。别忘了测试第一个数和最后一个数 真的很难想到 
    {
        for(int i=1; i<n; i++) //数组输出单独输出a[n]免得多一个空格出来 
            cout<<a[i]<<" ";
        cout<<a[n]<<endl;
    }
    else
    {
        for(int i=2; i<=n; i++)//从2开始遍历 
        {
            if(!vis[i]&&isp(i+a[s]))   //如果i没有用过,并且与前一个数之和为素数
            {
                a[s+1]=i;			//第一次s=1 a[s+1]相当于从2开始到a[n]{s==n递归边界} 
                vis[i]=1;              //标记 相当于此时这个i代表的值已经用了 
                dfs(s+1);				//从2到n 
                vis[i]=0;              //一种情况已完成或是中断 清除标记进行下一次 
            }
        }
    }
}
int main()
{
    int t=0;
    while(cin>>n)
    {
        memset(vis,0,sizeof(vis));
        a[1]=1;
        if(t!=0) cout<<endl;            //一定注意输出格式 每一个case后面要换行 第一次不用 
        t++;
        cout<<"Case "<<t<<":"<<endl;
        dfs(1);//从1开始 
    }
    return 0;
}

最让人头大的就是下面一题(我太菜了
想着一次性在一个数组里面标记所有情况 反而更麻烦

HDU 2553:N皇后问题

扫描二维码关注公众号,回复: 9005591 查看本文章

题目大意:在一个n*n棋盘上下n个棋(棋无差别),使这些棋互不同行,互不同列,互相不在一条斜对角线上,给你一个n,问有几种棋的摆法。

解题思路:n*n的棋盘下n个棋,则第一个棋子一定下在第一行,所以扫第一行n种情况,往下符合题意地蔓延(注意该题要提前将数据打好表来做,要不然会超时)。

判断皇后是否安全,即检查同一列、同一对角线是否已有皇后,
建立标志数组b[1…8]控制同一列只能有一个皇后,
若两皇后在同一对角线上,则其行列坐标之和或行列坐标之差相等,
故亦可建立标志数组c[1…16]、d[-7…7]控制同一对角线上只能有一个皇后。

如果斜线不分方向,则同一斜线上两皇后的行号之差的绝对值与列号之差的绝对值相同。
在这种方式下,要表示两个皇后I和J不在同一列或斜线上的条件可以描述为:
A[I]<>A[J] AND ABS(I-J)<>ABS(A[I]-A[J]) --------
{I和J分别表示两个皇后的行号}

#include<stdio.h>
#include<iostream>
using namespace std;
bool d[100]={0},b[100]={0},c[100]={0};
int sum=0,a[100];	int n;
int ans[20];
int search(int i)
{
  int j;
  for (j=1;j<=n;j++)                                              //每个皇后都有8位置(列)可以试放
if((!b[j])&&(!c[i+j])&&(!d[i-j+n-1]))                   //寻找放置皇后的位置,两个对角线和列都满足
//由于C++不能操作负数组,因此考虑加n-1
		{                                                                  //放置皇后,建立相应标志值
			a[i]=j;                                                          //摆放皇后
			b[j]=1;                                                         //宣布占领第j列
			c[i+j]=1;                                                      //占领两个对角线
			d[i-j+n-1]=1;
			if (i==n) sum++;          //方案数累加1        //n个皇后都放置好,输出
			else search(i+1);                                      //继续递归放置下一个皇后
			b[j]=0;                                                        //递归返回即为回溯一步,当前皇后退出
			c[i+j]=0;
			d[i-j+n-1]=0;
		}
}

int main()
{
	for(n=1;n<=10;n++){ //打表
	sum=0;
	search(1); 
	ans[n]=sum;
}
	while(scanf("%d",&n)!=EOF&&n!=0)
	{
		cout<<ans[n]<<endl;
	}
	return 0;                                               
}

//因为是1-10 打表交
//直接交超时 


总结
棋盘这种不要用二维数组,直接以行为标准,通过判断其他条件是否满足
能分开判断就分开判断 简洁明了
如果数位不是很大 就打表提交 免得时间超

数油田的就只是标记了就不用管了 走一次就完了

发布了7 篇原创文章 · 获赞 4 · 访问量 338

猜你喜欢

转载自blog.csdn.net/weixin_42727032/article/details/86531324
今日推荐