1.BFS
1.1 BFS模板
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn=100;
bool vst[maxn][maxn]; //访问标记
int dir[4][2]={0,1,0,-1,1,0,-1,0}; //方向向量,有些题目不需要
struct State //BFS 队列中的状态数据结构
{
int x,y; //坐标位置
int Step_Counter; //搜索步数统计器
};
State a[maxn];
/* 是否需要视题目而定
bool CheckState(State s) //约束条件检验
{
if(!vst[s.x][s.y]&&...) //满足条件
return 1;
else //约束条件冲突
return 0;
}
*/
void bfs(State st)
{
queue<State>q; //BFS队列
State now,next; //定义2个状态,当前和下一个
st.Step_Counter=0; //计数器清零
q.push(st); //入队 ,,,,,,另一种不带参数的写法是将初始的状态进入队列
vst[st.x][st.y]=1; //访问标记
while(!q.empty())
{
now=q.front(); //取队首元素进行扩展
if(now==G) //出现目标态,此时为Step_Counter的最小值,可以退出即可
{
...... //做相关处理
return;
}
for(int i=0;i<4;i++)
{
next.x=now.x+dir[i][0]; //按照规则生成下一个状态
next.y=now.y+dir[i][1];
next.Step_Counter=now.Step_Counter+1; //计数器加1
if(CheckState(next)) //如果状态满足约束条件则入队
{
q.push(next);
vst[next.x][next.y]=1; //访问标记
}
}
q.pop(); //队首元素出队
}
1.2 例题
1.2.1 非常可乐
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 19932 Accepted Submission(s): 8063
Problem Description
大家一定觉的运动以后喝可乐是一件很惬意的事情,但是seeyou却不这么认为。因为每次当seeyou买了可乐以后,阿牛就要求和seeyou一起分享这一瓶可乐,而且一定要喝的和seeyou一样多。但seeyou的手中只有两个杯子,它们的容量分别是N 毫升和M 毫升 可乐的体积为S (S<101)毫升 (正好装满一瓶) ,它们三个之间可以相互倒可乐 (都是没有刻度的,且 S==N+M,101>S>0,N>0,M>0) 。聪明的ACMER你们说他们能平分吗?如果能请输出倒可乐的最少的次数,如果不能输出”NO”。
Input
三个整数 : S 可乐的体积 , N 和 M是两个杯子的容量,以”0 0 0”结束。
Output
如果能平分的话请输出最少要倒的次数,否则输出”NO”。
Sample Input
7 4 3
4 1 3
0 0 0
Sample Output
NO
3
Author
seeyou
解题代码
#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=500;
//方向向量,有些题不需要
int dir[4][2]={0,1,0,-1,1,0,-1,0};
//BFS队列中的状态数据结构
int vis[maxn][maxn];
struct State
{
int S,N,M;
int step;
};
int s,n,m;
int res=-1;//保存最终结果
//约束条件检验
//bool check(State s);
queue<State>q;
void bfs()
{
//定义两个状态,现在和下一个
//将初始状态进入队列
memset(vis,0,sizeof(vis));
State now,next;
q.push((State){s,0,0,0});
vis[0][0]=1;
while(!q.empty())
{
//取队首元素进行扩展
now=q.front();
//判断是否出现目标状态,此时为step最小值,可以退出即可
if((now.S==s/2&&now.M==s/2)||(now.S==s/2&&now.N==s/2)||(now.M==s/2&&now.N==s/2))
{
res=now.step;
//将剩余队列清空
while(!q.empty())
{
q.pop();
}
return;
}
q.pop();
//开始进行搜索过程
//本题主要是对瓶S,N,M有六种操作 S->N,S->M,N->S,N->M,M->S,M->N;
//如果N瓶还未装满
if(now.N<n)
{
//此过程又可以分为S可以装满N和S不可以装满N以及M可以装满N和M不可以装满N
if(now.S>n-now.N&&!vis[n][now.M])
{
vis[n][now.M]=1;
q.push((State){now.S-n+now.N,n,now.M,now.step+1});
}
if(now.S<n-now.N&&!vis[now.N+now.S][now.M])
{
vis[now.S+now.N][now.M]=1;
q.push((State){0,now.S+now.N,now.M,now.step+1});
}
if(now.M>n-now.N&&!vis[n][now.M-n+now.N])
{
vis[n][now.M-n+now.N]=1;
q.push((State){now.S,n,now.M-n+now.N,now.step+1});
}
if(now.M<n-now.N&&!vis[now.M+now.N][0])
{
vis[now.M+now.N][0]=1;
q.push((State){now.S,now.M+now.N,0,now.step+1});
}
}
//如果M瓶还没装满
if(now.M<m)
{
if(now.S>m-now.M&&!vis[now.N][m])
{
vis[now.N][m]=1;
q.push((State){now.S-m+now.M,now.N,m,now.step+1});
}
if(now.S<m-now.M&&!vis[now.N][now.S+now.M])
{
vis[now.N][now.S+now.M]=1;
q.push((State){0,now.N,now.S+now.M,now.step+1});
}
if(now.N>m-now.M&&!vis[now.N-m+now.M][m])
{
vis[now.N-m+now.M][m]=1;
q.push((State){now.S,now.N-m+now.M,m,now.step+1});
}
if(now.N<m-now.M&&!vis[0][now.M+now.N])
{
vis[0][now.M+now.N]=1;
q.push((State){now.S,0,now.M+now.N,now.step+1});
}
}
//对于S瓶而言,其他瓶不可能把S装满
if(now.S<s)
{
if(now.N<s-now.S&&!vis[0][now.M])
{
vis[0][now.M]=1;
q.push((State){now.N+now.S,0,now.M,now.step+1});
}
if(now.M<s-now.S&&!vis[now.N][0])
{
vis[now.N][0]=1;
q.push((State){now.S+now.M,now.N,0,now.step+1});
}
}
}
}
int main()
{
while(~scanf("%d%d%d",&s,&n,&m))
{
res=-1;
if(s==0&&n==0&&m==0)
break;
if(s%2!=0)
{
printf("NO
");
continue;
}
bfs();
if(res==-1)
{
printf("NO
");
continue;
}
else
{
printf("%d
",res);
}
}
return 0;
}
1.2.2胜利大逃亡(续)
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 9963 Accepted Submission(s): 3593
Problem Description
Ignatius再次被魔王抓走了(搞不懂他咋这么讨魔王喜欢)……
这次魔王汲取了上次的教训,把Ignatius关在一个n*m的地牢里,并在地牢的某些地方安装了带锁的门,钥匙藏在地牢另外的某些地方。刚开始Ignatius被关在(sx,sy)的位置,离开地牢的门在(ex,ey)的位置。Ignatius每分钟只能从一个坐标走到相邻四个坐标中的其中一个。魔王每t分钟回地牢视察一次,若发现Ignatius不在原位置便把他拎回去。经过若干次的尝试,Ignatius已画出整个地牢的地图。现在请你帮他计算能否再次成功逃亡。只要在魔王下次视察之前走到出口就算离开地牢,如果魔王回来的时候刚好走到出口或还未到出口都算逃亡失败。
Input
每组测试数据的第一行有三个整数n,m,t(2<=n,m<=20,t>0)。接下来的n行m列为地牢的地图,其中包括:
. 代表路
* 代表墙 @ 代表Ignatius的起始位置 ^ 代表地牢的出口 A-J 代表带锁的门,对应的钥匙分别为a-j a-j 代表钥匙,对应的门分别为A-J每组测试数据之间有一个空行。
Output
针对每组测试数据,如果可以成功逃亡,请输出需要多少分钟才能离开,如果不能则输出-1。
Sample Input
4 5 17
@A.B.
a*.*.
*..*^
c..b*
4 5 16
@A.B.
a*.*.
*..*^
c..b*
Sample Output
16
-1
思路:
BFS+状态压缩
对钥匙状态压缩,因为有10个钥匙,所以钥匙串为10位的2进制数串。
在搜索过程中会碰到3种有效情况,碰到路径,碰到钥匙,碰到门;
- 碰到路径,进行正常的标记操作
- 碰到钥匙,对钥匙串进行或运算,把碰到的钥匙加到钥匙串
- 碰到门,进行与运算,查看是否有相应的钥匙
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<queue>
using namespace std;
const int maxn=25;
char ma[maxn][maxn];
bool vis[maxn][maxn][1025];//10种钥匙的状态
int m,n,t;
int sx,sy;
struct State
{
int x,y;
int step,key;
};
int dir[4][2]={1,0,-1,0,0,1,0,-1};
bool check(int x,int y)
{
if(x>=0&&x<n&&y>=0&&y<m&&ma[x][y]!='*')
return true;
else return false;
}
int bfs()
{
int k;
memset(vis,false,sizeof(vis) );
queue<State>q;
State now,next;
now.x=sx;
now.y=sy;
now.key=0;
now.step=0;
q.push(now);
vis[sx][sy][0]=1;
while(!q.empty())
{
now=q.front();
//出现目标状态
q.pop();
if(ma[now.x][now.y]=='^')
{
if(now.step<t)
return now.step;
}
if(now.step>=t) break;
for(int i=0;i<4;i++)
{
next.x=now.x+dir[i][0];
next.y=now.y+dir[i][1];
next.step=now.step+1;
next.key=now.key;
if(check(next.x,next.y))
{
//碰到三种情况 碰到钥匙 碰到门 碰到路径
//碰到门的情况,钥匙与门进行与操作
if(ma[next.x][next.y]<='J'&&ma[next.x][next.y]>='A')
{
k=ma[next.x][next.y]-'A';
if((next.key>>k)&1&&!vis[next.x][next.y][next.key])
{
vis[next.x][next.y][next.key]=true;
q.push(next);
}
}
//碰到钥匙的情况,采用或运算放入钥匙
else if(ma[next.x][next.y]<='j'&&ma[next.x][next.y]>='a')
{
k=1<<(ma[next.x][next.y]-'a');
next.key|=k;
if(!vis[next.x][next.y][next.key])
{
vis[next.x][next.y][next.key]=true;
q.push(next);
}
}
else
{
if(!vis[next.x][next.y][next.key])
{
vis[next.x][next.y][next.key]=true;
q.push(next);
}
}
}
}
}
return -1;
}
int main()
{
int i,j;
freopen("test.txt","r",stdin);
while(scanf("%d%d%d",&n,&m,&t)!=EOF)
{
for(int i=0;i<n;i++)
{
scanf("%s",ma[i]);
for(j=0;j<m;j++)
{
if(ma[i][j]=='@')
{
sx=i;
sy=j;
}
}
}
printf("%d
",bfs());
}
return 0;
}
2. DFS
2.1 DFS模板
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
const int maxn=100;
bool vst[maxn][maxn]; //访问标记
// bool vis[maxn]; //一维状态标记
int map[maxn][maxn]; //坐标范围
int dir[4][2]={0,1,0,-1,1,0,-1,0}; //方向向量,(x,y)周围的四个方向
bool CheckEdge(intx,int y) //边界条件和约束条件的判断
{
if(!vst[x][y]&&...) //满足条件
return 1;
else // 与约束条件冲突
return 0;
}
void dfs(int x,int y)
{
vst[x][y]=1; //标记该节点被访问过
if(map[x][y]==G) //出现目标态G
{
...... //做相应处理
return;
}
for(int i=0;i<4;i++)
{
if(CheckEdge(x+dir[i][0],y+dir[i][1])) //按照规则生成下一个节点
dfs(x+dir[i][0],y+dir[i][1]);
}
return;//没有下层搜索节点,回溯
}
int main()
{
......
return 0;
}
2.2 DFS 例题
2.2.1 棋盘问题
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 58295 Accepted: 28027
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<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
const int maxn=500;
//设置访问标记
//bool vis[maxn][maxn];
bool vis[maxn];//每一列棋子的访问标志
char m[maxn][maxn];
//设置方向向量
int dir[4][2]={1,0,-1,0,0,1,0,-1};
//已放棋子的数目
int cnt;
//放置棋子的方案数
int sum;
int k,n;
//边界约束条件
/*
bool CheckEdge(int x,int y)
{
if(!vis[x][y]&&.....) //满足条件
return 1;
else //与约束条件冲突
return 0;
}
*/
void dfs(int s)
{
//检测是否满足条件
if(cnt==k)
{
sum++;
return;
}
else
{
//判断是否越界
if(s>=n)return;
else
{
//开始搜索过程
//首先尝试将棋子放在0-n-1的某一列
for(int i=0;i<n;i++)
{
//如果当前列有棋盘而且没有被标记
if(m[s][i]=='#'&&!vis[i])
{
vis[i]=1;
cnt++;
//继续回溯搜索下一步
dfs(s+1);
//经过一轮搜索没结果则回溯
cnt--;
vis[i]=0;
}
}
dfs(s+1);
}
}
}
int main()
{
while(cin>>n>>k)
{
memset(vis,0,sizeof(vis));
sum=cnt=0;
if(n==-1&&k==-1)
break;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>m[i][j];
dfs(0);
cout<<sum<<endl;
}
}