文章目录
dfs和回溯算法
dfs和暴力枚举的区别在于:暴力枚举是把每一种情况都列举出来,不管这个情况是否合理。而dfs是在暴力枚举下的优化算法,它会对一些不符合条件进行去除。(剪枝)
dfs模板
dfs本质上是一颗递归树,且是深度优先遍历。
因此核心就是把每一个分支的空填上
void dfs(int k)
{
if(一个分支结束了)
{
判断最优解/记录答案;//判断最优解类似res = max(res,a)
return;
}
for(枚举这一层中可以填的选项)
{
if(这个选项是合理的)
{
填上;
dfs(k+1);//去到下一层
恢复现场;
}
}
}
四阶数独
写一个程序来看四阶数独有几种填法。
考点:dfs
注意点:
1.一维数组的下标(从1开始)转二维数组row公式为:
row = (x - 1) / n + 1;//n是二维数组的长度
2.一维数组的下标(从1开始)转二维数组col公式为:
col = (x - 1) % n + 1;//n是二维数组的长度
3.分块下标
block = (row - 1) / 2 * 2 + (col - 1) / 2 + 1
4.dfs中判断这个空是否合法是最困难的。可以模仿这种格式,用数组来记录是否合法。
5.虽然题目中的四阶数独给我们的感觉是二维数组,但是创建一维数组比较好。有通用性
#include <iostream>
using namespace std;
int board[25];
int r[5][5], c[5][5], b[5][5];
int ans;
void dfs(int x)
{
if (x > 16)
{
ans++;
//输出所有方案
/*for (int i = 1; i <= 16; i++)
{
cout << board[i];
if (i % 4 == 0) puts("");
}
puts("");*/
return;
}
int row = (x - 1) / 4 + 1;
int col = (x - 1) % 4 + 1;
int block = (row - 1) / 2 * 2 + (col - 1) / 2 + 1;
for (int i = 1; i <= 4; i++)
{
if (r[row][i] == 0 && c[col][i] == 0 && b[block][i] == 0)
{
board[x] = i;
r[row][i] = 1; c[col][i] = 1; b[block] [i] = 1;
dfs(x + 1);
r[row][i] = c[col][i] = b[block][i] = 0;
}
}
}
int main()
{
dfs(1);
cout << ans;
}
8皇后
考点:dfs
注:
1.对角线的判断方法可以用一维数组来判断对角线是否有棋子已经存在了。正对角线一个,副对角线一个
2.打印二维字符数组,可以用puts来打印,二维数组每一行其实就相当于一个字符串,puts可以直接打印出来。并最后加上换行。
代码:
#include <iostream>
#include <cstring>
using namespace std;
char board[10][10];
int col[100], dg[100], udg[100];
int ans;
void dfs(int k)
{
if (k == 8)
{
/*for (int i = 0; i < 8; i++) puts(board[i]);
puts("");*/
ans++;
return;
}
for (int i = 0; i < 8; i++)
{
if (col[i] == 0 && dg[i + k] == 0 && udg[i - k + 8] == 0)
{
board[k][i] = '*';
col[i] = dg[i + k] = udg[i - k + 8] = 1;
dfs(k + 1);
col[i] = dg[i + k] = udg[i - k + 8] = 0;
board[k][i] = '#';
}
}
}
int main()
{
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
board[i][j] = '#';
dfs(0);
cout << ans;
}
bfs
bfs模板
这是其中一个模板,可以用size把同一层的内容一起处理。
bool vis[];
queue<typename> q;
q.push(第一个元素)
while(!q.empty())
{
int sz = q.size();//每一层的大小
for(int i = 0; i < sz; i++)
{
int t = q.front();
q.pop();
if(t == 目标) return 最小步数或者其他东西
for(元素 : t的周围的元素)
{
if(符合某种条件 && vis[t] == false)
q.push(元素);
vis[元素] = true;
}
}
step++;//要维护的步骤次数++
}
这是第二个模板,state可以用一个结构体来管理,像上面的step一般都会是这个结构体的成员。
q.push(初始状态);
while(!q.empty())
state u = q.front();
q.pop();
for(初识状态旁边的所有可以扩展的状态)
if(isvalid())
q.push(v);
马的遍历
有一个n×m的棋盘,在某个点上有一个马,计算出马到达棋盘任意一个点最少要走几步。
马的遍历
考点:bfs
模板题。
在bfs模板当中维护步数。
步数之间的关系如下:
当前的步数 = 上一次的步数 + 1
ac代码:
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 410;
int g[N][N];
queue<pair<int, int> > q;
int dir[8][2] = { {2,1},{1,2},{-1,2},{-2,1},{-2,-1},{-1,-2},{1,-2},{2,-1}};
bool vis[N][N];
int step = 1;
void bfs(int n, int m, int x, int y)
{
q.push({ x,y });
vis[x][y] = true;
g[x][y] = 0;
while (!q.empty())
{
int sz = q.size();
for (int i = 0; i < sz; i++)
{
int x = q.front().first, y = q.front().second;
q.pop();
for (int i = 0; i < 8; i++)
{
int newx = x + dir[i][0], newy = y + dir[i][1];
if (newx >= 0 && newy >= 0 && newx < n && newy < m && vis[newx][newy] == false)
{
q.push({ newx,newy });
g[newx][newy] = step;
vis[newx][newy] = true;
}
}
}
step++;
}
}
int main()
{
int n, m, x, y;
cin >> n >> m >> x >> y;
x--, y--;
memset(g, -1, sizeof(g));
bfs(n, m, x, y);
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
printf("%-5d", g[i][j]);
puts("");
}
}
奇怪的电梯
考点bfs
注:要学会用结构体来管理一些信息,会比较清晰一些。
用模板1来管理按按钮的次数
#include <iostream>
#include <queue>
using namespace std;
const int N = 210;
int n, a, b;
int f[N];
int ans;
queue<int> q;
bool vis[N];
int dir[2] = { 1,-1 };
int bfs(int a, int b)
{
q.push(a);
while (!q.empty())
{
int sz = q.size();
for (int i = 0; i < sz; i++)
{
int t = q.front();
q.pop();
if (t == b) return ans;
for (int i = 0; i < 2; i++)
{
int opt = t + f[t - 1] * dir[i];
if (opt >= 1 && opt <= b && vis[opt] == false)
{
q.push(opt);
vis[opt] = true;
}
}
}
ans++;
}
return -1;
}
int main()
{
cin >> n >> a >> b;
for (int i = 0; i < n; i++) cin >> f[i];
cout << bfs(a, b);
}
用模板2来管理按按钮的次数
#include <iostream>
#include <queue>
using namespace std;
const int N = 210;
int n, a, b;
int f[N];
int ans;
bool vis[N];
int dir[2] = { 1,-1};
struct node { int floor; int d; };
queue<node> q;
int bfs(int a, int b)
{
q.push({ a,0 });
while (!q.empty())
{
int floor = q.front().floor;
int d = q.front().d;
q.pop();
if (floor == b) return d;
for (int i = 0; i < 2; i++)
{
int opt = floor + f[floor - 1] * dir[i];
if (opt >= 1 && opt <= b && vis[opt] == false)
{
q.push({ opt,d + 1 });
vis[opt] = true;
}
}
}
return -1;
}
int main()
{
cin >> n >> a >> b;
for (int i = 0; i < n; i++) cin >> f[i];
cout << bfs(a, b);
}
流星雨
Meteor Shower S
这道题目看起来很复杂,因为在bfs上引入了时间的概念。因此模板1那种形式写法就不好写了,用一个结构体去描述会方便许多。
注:
1.这道题扩展的限定条件是:
走到这一个格子的时间 < 爆炸产生的时间 - 1
原因是:走到下一个格子是需要一秒时间的
2.要初始化一个death数组,它用来记录每个格子爆炸的时间,记录的时候,由于它们有可能会互相影响,因此要用
death[x][y] = min(death[x][y], time);
ac代码:
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 50010, M = 310;
int g[M][M], death[M][M];
bool vis[M][M];
int dir[4][2] = { {1,0}, {-1,0},{0,1},{0,-1} };
struct node
{
int x;
int y;
int t;
}Node[N];
queue<node> q;
int bfs()
{
q.push({ 0,0,0 });
vis[0][0] = true;
while (!q.empty())
{
node tmp = q.front();
int x = tmp.x, y = tmp.y, t = tmp.t;
q.pop();
if (death[x][y] == 0x7f7f7f7f) return t;
for (int i = 0; i < 4; i++)
{
int newx = x + dir[i][0], newy = y + dir[i][1];
if (newx >= 0 && newy <= 300 && newy >= 0 && newy <= 300 && vis[newx][newy] == false && (tmp.t < death[newx][newy] - 1))
{
q.push({ newx,newy,t + 1 });
vis[newx][newy] = true;
}
}
}
return -1;
}
int main()
{
memset(death, 0x7f, sizeof death);
int n;
cin >> n;
for (int i = 0; i < n; i++)
{
int x, y, t;
cin >> x >> y >> t;
Node[i] = { x, y , t };
death[x][y] = min(t, death[x][y]);
for (int i = 0; i < 4; i++)
{
int newx = x + dir[i][0], newy = y + dir[i][1];
if (newx >= 0 && newx <= 300 && newy >= 0 && newy <= 300) death[newx][newy] = min(death[newx][newy], t);
}
}
cout << bfs();
}