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皇后问题
题目大意:在一个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 打表交
//直接交超时
总结
棋盘这种不要用二维数组,直接以行为标准,通过判断其他条件是否满足
能分开判断就分开判断 简洁明了
如果数位不是很大 就打表提交 免得时间超
数油田的就只是标记了就不用管了 走一次就完了