搜索小贴士:
DFS、BFS其实是图的一种遍历方式,两种算法均可以遍历整张图,是非常暴力的算法,能解决大多数对时间要求不高的问题,但其应用场景却不太一样,由于他们两种的算法特性导致的,如下:
1.问最短、最少之类的问题,一般要想到BFS,BFS搜索是逐层搜索,达到本层时所花费的value值是一样的,由于BFS借助队列实现,而队列有先进先出的特性,所以当找到结果时,是从上一层继承下来的,一定是当前问题的最少花费。求最短路问题时,优先用bfs,因为dfs需要遍历所有情况(此处思考一下为什么)才能找到最短路(spfa算法正是借助bfs来实现的)
2.DFS一般会伴随着回溯,只需要记得DFS是一条路走到黑,走不通时返回到最近一个已访问过的且有其他岔路的节点(至少有一条岔路必须还未访问过),然后访问该岔路
3.DFS是一种可以“后悔”的算法,通常都是使用回溯来后悔的
那么,为什么DFS需要遍历所有情况才能找到最短路?因为他每一次都是一条路走到黑,在没有遍历全部节点时根本不知道那一条路是最近的
DFS模板:
void dfs(当前位置,节点列表){
if(满足结束条件){
记录结果;
return;
}
for(选择:节点列表){
处理当前节点;
dfs(当前位置,节点列表);
撤销选择;//回溯的精髓
}
}
BFS模板:
int vis[x];//标记数组,用来标记该节点是否曾经入队,避免节点重复入队导致死循环
queue<int>q;//或可以用数组辅助实现队列,int q[100],int pre=0,rear=0;队头和队尾
while(!q.empty()){
int now=q.front();//取队首元素
q.pop();//弹出队首元素
步骤1.找出当前节点走一步可以接触到的地方即邻接点
步骤2.在vis数组中查询步骤1找到的节点
步骤3.将未插入vis数组的节点入队
}
思路:n皇后变形题,区别在于不需要每行都要放上棋子,所以每一行我们可以选择放or不放
不需要去判断每一行是否有其他棋子放置,因为我们是一行放一个,放的时候用istrue去判断当前位置是否合法,再设置一个标记数组记录当前位置,递归返回后记得取消标记
#include<iostream>
using namespace std;
char mp[10][10];
int vis[10][10];
int ans = 0;
bool istrue(int x,int y,int n){
//同一列
for (int i = x-1; i > -1; --i){
if (vis[i][y])return 0;
}
return 1;
}
void dfs(int now,int n,int k){//now是当前行号,n*n棋盘,k个棋子
if (k == 0){//只要放完棋子就可以记录
ans++;
return;
}
if(now>=n)return;
for (int i = 0; i < n; ++i){
if (mp[now][i]=='#'&&istrue(now, i,n)){
vis[now][i] = 1;//now是当前行号
dfs(now + 1, n, k - 1);//放下
vis[now][i] = 0;//回溯
}
}
dfs(now+1,n,k);//不放
}
int main(){
int n=0, m=0;
while (cin >> n >> m){
if (n == -1 && m == -1)break;
for (int i = 0; i < n; ++i){
for (int j = 0; j < n; ++j){
cin >> mp[i][j];
}
}
dfs(0,n,m);
cout<<ans<<endl;
ans=0;
}
return 0;
}
题意:一个三维牢笼,求从S开始寻到E的最短路径,该题输入数据时不用读取空格。
思路:迷宫问题变形,难点在于理解题目意思,对于三维牢笼的建图,我们可以采用三维数组,三维数组a[z][x][y]可以看成z个二维数组的集合(z即可以看成z层牢笼),对于二维迷宫问题,是处于平面上的,移动时只有四个方向,上下左右,而三维迷宫问题则可以移动6个方向,如下图,z坐标轴上的移动可以看成从某一层移动到其他层,其他坐标轴则是我们常见的二维迷宫问题了,在二维迷宫问题上面多了一个Z坐标轴。
定义结构体node,存储位置和当前步数
#include<iostream>
#include<stdio.h>
#include<malloc.h>
#include<queue>
#include<string>
using namespace std;
struct node{
int z,x, y;
int step;
};
char mp[40][41][41];
int vis[41][41][41];
int mv[6][3] = { { -1, 0, 0 }, { 1, 0, 0 }, { 0, -1, 0 }, { 0, 1, 0 }, { 0, 0, -1 }, { 0, 0, 1 } };
//int vis[40];
int main(){
int n = 0, m = 0, o = 0;
while (cin >> n >> m >> o , m+n+o){
int sz, sx, sy,ez,ex,ey;
for (int i = 0; i < n; ++i){
for (int j = 0; j < m; ++j){
for (int k = 0; k < o; ++k){
cin >> mp[i][j][k];
if (mp[i][j][k] == 'S'){
sz = i, sx = j, sy = k;
}
/*else if (mp[i][j][k] == 'E'){
ez = i, ex = j, ey = k;
}*/
}
}
//string temp; cin >> temp;
}
//BFS
int flag = 0;
memset(vis, 0, sizeof(vis));
queue<node>q;
node now;
now.z = sz, now.x = sx, now.y = sy, now.step = 0;
vis[sz][sx][sy] = 1;
q.push(now);
while (!q.empty()){
now = q.front(); q.pop();
if (mp[now.z][now.x][now.y] == 'E'){
cout << "Escaped in " << now.step <<" minute(s)."<<endl ;
flag = 1;
}
for (int i = 0; i < 6; ++i){
node temp = now;
int z = now.z + mv[i][0], x = now.x + mv[i][1],y = now.y + mv[i][2] ;
if (z>-1 && z<n&&x>-1 && x<m&&y>-1 && y<o&&vis[z][x][y] == 0 && (mp[z][x][y] == '.' || mp[z][x][y] == 'E')){
now.z = z, now.x = x, now.y = y,now.step=now.step+1;
q.push(now);
now = temp;
vis[z][x][y] = 1;
}
}
}
if (!flag)cout << "Trapped!" << endl;
//q.
//BFS
/*int flag = 0;
char *q = new char[40]();
//int *vis = new int[40](),*step=new int[40]();
int rear = 0,pre=0;
q[rear++] = mp[0][0][0];
while (pre != rear){
}*/
}
return 0;
}
Catch That Cow
题目大意:输入两个数n,m,求n到m的最短距离(n只有三种走法,前后移动一个位置或移动到2*n的位置)
思路:无脑搜索即可,只需要注意1.不能移动到小于0的位置;2.必须小于等于abs(n-m),此处的n是初始n,因为abs(n-m)就是一格一格移动到终点,没有花里胡哨的步骤;3.不可移动至(max(n,m)+1)*2的位置
BFS:
#include<iostream>
#include<queue>
#include<string>
#include<map>
using namespace std;
int ans;
map<int, int>dp;
int n;
struct node{
int pos, step;
};
int abs(int x, int y){
if (x < y)return y-x;
return x-y;
}
int max(int x, int y){
if (x>y)return x;
return y;
}
int main(){
int m ,maxn;
//while (cin >> n >> m){
cin >> n >> m;
maxn = max(m,n)+1;
int flag = 0;
node *q = new node[400000 + 10]();
int *vis = new int[400000 + 10]();
int pre = 0, rear = 1;
q[0].pos = n, q[0].step = 0;
vis[n] = 1;
while (pre != rear){
node temp = q[pre++];
if (temp.pos == m){
cout<< temp.step<<endl;
break;
}
for (int i = 0; i < 3; ++i){
node now = temp;
if (i == 0){
now.pos += 1;
}
else if (i == 1){
now.pos -= 1;
}
else{
now.pos *= 2;
}
now.step++;
//if (now.step > abs(n - m) || now.pos<0 || now.pos>maxn || vis[now.pos])continue;
if (now.pos >= 0 && !vis[now.pos]&&now.step<=abs(n-m)&&now.pos<=maxn*2){
vis[now.pos] = 1;
q[rear++] = now;
}
}
}
//}
return 0;
}
Curling 2.0
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<vector>
int w,h,sx,sy,ex,ey;
int maze[25][25],flag,ans;
int mv[4][2] = { {-1,0}, {0,-1}, {1,0}, {0,1} };//上左,下,右
int min(int x, int y){
return x < y ? x : y;
}
void dfs(int des,int cnt,int x,int y){//des表示方向
//if (flag)return;
if (x == ex&&y == ey){
flag = 1;
ans = min(cnt, ans);
return;
}if (cnt >= 10)return;
for (int i = 0; i < 4; ++i){
//if (flag)return;
int nx = x + mv[i][0],ny=y+mv[i][1];
if (nx < 0 || nx >= h || ny < 0 || ny >= w)continue;
//if (x == sx&&y == sy){
if (maze[nx][ny] == 1)continue;//紧邻墙壁无法移动;
//}
//if (i == 0){
int yuejie = 0;
while (maze[nx][ny] == 0||maze[nx][ny]==2){//继续往该方向走下去,只要不越界
nx += mv[i][0], ny += mv[i][1];
if (nx < 0 || nx >= h || ny < 0 && ny >= w){
//nx -= mv[i][0], ny -= mv[i][1];//越界了 退回去
yuejie = 1;
break;
}
if (x == ex&&y == ey){
flag = 1;
ans = min(cnt, ans);
//return;
}
}if (yuejie)continue;//越界了说明走不通
int pos = 0;
if (maze[nx][ny] == 1){
maze[nx][ny] = 0;
pos = 1;
//dfs(i, cnt + 1, nx, ny);
dfs(i, cnt + 1, nx - mv[i][0], ny - mv[i][1]);
}
else{
dfs(i, cnt + 1, nx, ny );
}
if (pos)maze[nx][ny] = 1;
}
}
using namespace std;
int main(){
int cnt = 0;
vector<int>temp;
while (cin >> w >> h,w&&h){
for (int i = 0; i < 25; ++i){
for (int j = 0; j < 25; ++j)maze[i][j] = 0;
}
flag = 0;
ans = 0x3f3f3f3f3f;
for (int i = 0; i < h; ++i){
for(int j = 0; j < w; ++j){
cin >> maze[i][j];
if (maze[i][j] == 2){
sx = i, sy = j;
// maze[i][j] = 0;
}
else if (maze[i][j] == 3){
ex = i, ey = j;
}
}
}
//if ()
dfs(0,0,sx,sy);
//cout << "结果"<<(++cnt)<<":";
/*if (flag)temp.push_back(ans);
else temp.push_back(-1);*/
if (flag)
cout << ans << endl;
else cout << -1 << endl;
}
//for (auto x : temp)cout << x << " ";
return 0;
}
Find The Multiple
题意:给定一个数字n,求n的倍数(只能由0和1组成)
思路:直接暴搜两种情况,从1开始求倍数,*10或*10+1,每个样例的最小值不会超过long long的范围,所以*18次后还没结果就可以返回咯
BFS的做法更简单,不需要判定超过longlong的次数,直接搜索就行,但是队列手动模拟的话就做不出来了
DFS:
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<vector>
using namespace std;
long long n;
long long ans;
int flag;
void dfs(long long now,int cnt){
if (flag)return;
if (now%n == 0){
flag = 1;
ans = now;
return;
}
if (cnt == 18)return;
dfs(now*10,cnt+1);
dfs(now * 10 + 1,cnt+1);
}
int main(){
while (cin >> n,n){
flag = 0; ans = 0;
dfs(1,0);
cout << ans << endl;
}
return 0;
}
BFS:
#include<iostream>
#include<queue>
using namespace std;
long long n;
struct node{
int cnt;
long long now;
};
int main(){
while (cin >> n,n){
queue<node>q;
node temp;
temp.now = 1, temp.cnt = 0;
q.push(temp);
while (!q.empty()){
temp = q.front(); q.pop();
if (temp.now%n == 0){
cout << temp.now << endl;
break;
}
node inq;
inq.now = temp.now * 10, inq.cnt = temp.cnt + 1;
q.push(inq);
inq.now++;
q.push(inq);
}
}
return 0;
}
A Knight's Journey
题意:给定一个棋盘,一个棋子,棋子只能按8个方向(下图中白色圆点就是马棋子从该位置开始可以走到的位置)移动,求该棋子是否能完整走完这个棋盘,若能,输出移动的完整路径。
ps:起点可以任意
思路:有些题解遍历了棋盘所有点作为起点去移动,虽然较为严谨但没有必要,直接从棋盘左上角移动就可以了,因为反推法:既然可以遍历到所有点,那么从任何位置开始都可以遍历完这一整张图
坑点:用string类型保存路径会TLE,必须用char数组,乌鱼子.....
#include<iostream>
#include<algorithm>
#include<queue>
#include<string>
#include<map>
using namespace std;
struct node{
int x, y,step;
//string s;
char s[100];
};
string ans;
int n, m, flag;
int vis[100][100];
int i;
int mv[8][2] = { { -1, -2 }, { 1, -2 }, { -2, -1 }, { 2, -1 }, { -2, 1 }, { 2, 1 }, { -1, 2 }, { 1, 2 } }; //必须
void dfs(int x, int y, node now){
if (flag)return;
if (now.step == n*m * 2){
flag = 1;
//ans = now.s;
cout << "Scenario #" << i << ":" << endl;
for (int i = 0; i < now.step; ++i){
if (now.s[i] != '\0')
cout << now.s[i];
}
cout << endl << endl;
//cout << now.s << endl << endl;
return;
}
for (int j = 0; j < 8 && !flag; ++j){
//node temp;
int temp_x = x + mv[j][0];
int temp_y = y + mv[j][1];
if (flag == 0 && vis[temp_x][temp_y] == 0 && temp_x>0 && temp_x <= n&&temp_y > 0 && temp_y <= m){
vis[temp_x][temp_y] = 1;
node temp = now;
temp.s[temp.step++] = (temp_y - 1 + 'A');
temp .s[temp.step++]= (temp_x + '0');
dfs(temp_x, temp_y,temp );
vis[temp_x][temp_y] = 0;
}
}
}
int main(){
int sum;
//int n = 0, m = 0;
cin >> sum;
for (i = 1; i <= sum; ++i){
cin >> n >> m;
for (int k = 0; k <= n; ++k){
for (int j = 0; j <= m; ++j)vis[k][j] = 0;
}
flag = 0;
//node temp; temp.x = 1, temp.y = 1;
vis[1][1] = 1;
node temp;
temp.s[0] = 'A', temp.s[1] = '1'; temp.step = 2;
dfs(1, 1, temp);
if (!flag){
cout << "Scenario #" << i << ":" << endl;
cout << "impossible" << endl<<endl;
}
}
return 0;
}
素数打表+BFS
题意:给两个数A,B,求A到B需要变化的最小次数,每次变化时都必须是素数
思路:先把10000内的素数打表,然后分个、十、百、千位去搜索,除千位以外都可0-9,千位不可取0
还是细节问题。注意数组别开的太小,给定的数>=1000,不能包含前导0。
#include<iostream>
using namespace std;
int T;
int n, m;
int prime[50000],vis[50000];
struct node{
int num,cnt;
};
int POW(int x, int y){
if (y == 0)return 1;
for (int i = 2; i <= y; ++i){
x *= 10;
}
return x;
}
int ans = 0;
node q[20000];
void bfs(){
//node *q = new node[20000]();
int pre = 0, rear = 1;
q[0].num = n, q[0].cnt = 0;
vis[n] = 1;
while (pre != rear){
node now = q[pre++];
if (now.num == m){
cout << now.cnt << endl;
ans = 1;
break;
}
int temp;
temp = now.num / 10 * 10;//个位
for (int i = 0; i < 10; ++i){
int temp2 = temp + i;
//temp += i;
if (vis[temp2] == 0 && prime[temp2]){
node p; p.num = temp2, p.cnt = now.cnt + 1;
q[rear++] = p;
vis[temp2] = 1;
}
}
temp = now.num / 100 * 100+now.num%10;//十位
for (int i = 0; i < 10; ++i){
int temp2 = temp + i * 10;
if (vis[temp2]==0 && prime[temp2]){
node p; p.num = temp2, p.cnt = now.cnt + 1;
q[rear++] = p; vis[temp2] = 1;
}
}
temp = now.num / 1000 * 1000 + now.num % 100;//百位
for (int i = 0; i < 10; ++i){
int temp2 = temp + i * 100;
if (vis[temp2]==0 && prime[temp2]){
node p; p.num = temp2, p.cnt = now.cnt + 1;
q[rear++] = p; vis[temp2] = 1;
}
}
temp = now.num % 1000;//千位
for (int i = 1; i < 10; ++i){
int temp2 = temp + i * 1000;
if (vis[temp2]==0 && prime[temp2]){
node p; p.num = temp2, p.cnt = now.cnt + 1;
q[rear++] = p; vis[temp2] = 1;
}
}
}
//delete(q);
}
int main(){
cin >> T;
int cnt = 0;
for (int i = 2; i <=20000; ++i){//打表
int flag = 0;
for (int j = 2; j < i; ++j){
if (i%j == 0){
flag = 1;
break;
}
}
if (!flag)
prime[i] = 1;
}
while (T--){
cin >> n >> m;
for (int i = 0; i <= 15000; ++i){
vis[i] = 0;
q[i].num = 0, q[i].cnt = 0;
}
ans = 0;
if (n == m){
cout << 0 << endl; continue;
}
bfs();
if (!ans)cout << "Impossible" << endl;
}
return 0;
}
题意:倒水问题,只有6种操作,均只对两个杯子A,B进行操作,只要两个杯子任意一杯的水量=目标水量,即判定找到,6种操作分别是1.倒满A杯,2.倒满B杯,3.倒空A,4.倒空B,5.A倒进B,6.B倒进A。注意:A倒B时,A当前水量>B当前剩余容量(不是水量),则溢出部分需要留在A内,B倒A同理
思路:题目要求最少的操作数,那必然是BFS,每一次都搜索这六种操作即可
注意继承前一个状态所走的路径,我之前忘记继承一直出错
#include<iostream>
using namespace std;
int T;
int n,m,C;
int vis[1000][1000];
struct node{
int a,b,cnt;
int s[10000];
};node q[10000];
int main(){
//while (cin >> n>> m>> C){
cin >> n >> m >> C;
int flag = 0;
int pre = 0, rear = 1;
q[0].a = 0, q[0].b = 0, q[0].cnt = 0;
vis[0][0] = 1;
while (pre != rear){
node now = q[pre++];
if (now.a == C || now.b == C){
// flag = 1;
cout << now.cnt << endl;
//1是倒1,2倒2,3是1to2,4是2to1,5是倒空1,6是倒空2
for (int i = 0; i < now.cnt; ++i){
if (now.s[i] == 1)cout << "FILL(1)" << endl;
else if (now.s[i] == 2)cout << "FILL(2)" << endl;
else if (now.s[i] == 3)cout << "POUR(1,2)" << endl;
else if (now.s[i] == 4)cout << "POUR(2,1)" << endl;
else if (now.s[i] == 5)cout << "DROP(1)" << endl;
else if (now.s[i] == 6)cout << "DROP(2)" << endl;
}
return 0;
break;
}
for (int i = 0; i < 6; ++i){
node temp=now;
int a = now.a, b = now.b;
if (i == 0){//倒满a
if (a == n)continue;
a = n;
if (!vis[a][b]){
temp.s[now.cnt] = 1;
temp.a = a, temp.b = b, temp.cnt = now.cnt + 1;
q[rear++] = temp;
vis[a][b] = 1;
}
}
else if (i == 1){//倒满b
//if (!b){
if (b == m)continue;
b = m;
if (!vis[a][b]){
//temp.s = now.s;
temp.s[now.cnt] = 2;
temp.a = a, temp.b = b, temp.cnt = now.cnt + 1;
q[rear++] = temp;
vis[a][b] = 1;
}
//}
}
else if (i == 2){//a to b
int sb = m - b;//b还能放多少
if (sb==0 || a==0)continue;//b放不下或a没有东西倒
if (a - sb >= 0){
a -= sb;
b = m;
}
else{
b += a;
a = 0;
}
if (!vis[a][b]){
temp.s[now.cnt] = 3;
temp.a = a, temp.b = b, temp.cnt = now.cnt + 1;
q[rear++] = temp;
vis[a][b] = 1;
}
}
else if (i == 3){//b to a
int sa = n - a;//a还能放多少
if (sa==0 || b==0)continue;//a放不下或b为空
if (b - sa >= 0){//b还有剩余
b -= sa;
a = n;
}
else{
a += b;
b = 0;
}
if (!vis[a][b]){
temp.s[now.cnt] = 4;
temp.a = a, temp.b = b, temp.cnt = now.cnt + 1;
q[rear++] = temp;
vis[a][b] = 1;
}
}
else if (i == 4){//倒空a
if (a==0)continue;
a = 0;
if (!vis[a][b]){
temp.s[now.cnt] = 5;
temp.a = a, temp.b = b, temp.cnt = now.cnt + 1;
q[rear++] = temp;
vis[a][b] = 1;
}
}
else if (i == 5){//倒空b
if (b==0)continue;
b = 0;
if (!vis[a][b]){
temp.s[now.cnt] = 6;
temp.a = a, temp.b = b, temp.cnt = now.cnt + 1;
q[rear++] = temp;
vis[a][b] = 1;
}
}
}
}
cout << "impossible" << endl;
//}
return 0;
}
题意:给定一张无向联通图,将图中所有节点分成两个集合,求连接两个集合的边最大之和是多少(即最大割问题:怎么把一张图切割成两个部分,并且相连的边之和最大)
ps:有道类似的题可以在力扣搜一下二分图匹配问题
思路:搜索两种状态即可,选择与不选择当前点加入集合1,可剪枝(看代码)
#include<algorithm>
#include<iostream>
using namespace std;
int n;
int maze[30][30],vis[30];
int ans;
void dfs(int now,int sum){
vis[now] = 1;//加入集合1,共两个集合,集合0和1
int temp = sum;
for (int i = 1; i <= n; ++i){
if (vis[i]){//同是集合1
temp -= maze[now][i];
}
else{
temp += maze[now][i];
}
}
ans = max(ans, temp);
if (temp < sum)return;//剪枝,此点加入该集合后使两集合的和变小,则不选择
for (int i = now + 1; i < n + 1; ++i){
dfs(i,temp);
vis[i] = 0;//回溯,本算法的精髓,即选与不选,给自己后悔的路
}
}
int main(){
//int n;
cin >> n;
for (int i = 1; i <= n; ++i){
for (int j = 1; j <= n; ++j){
cin >> maze[i][j];
}
}
dfs(1,0);
cout <<ans <<endl;
return 0;
}