目录
2.数黑砖 https://vjudge.net/contest/572876#problem/I
5.湖的数量 [USACO10OCT] Lake Counting S - 洛谷
2,八数码问题 https://www.luogu.com.cn/problem/P1379
一,贪心
题目练习 CSDN
贪心并没有具体的模板,只是一种思考问题的方式,多多做题可以帮助你找到更好的贪心策略。
二,二叉树
二叉树的讲解推荐一篇博客,讲的非常好。(http://t.csdn.cn/SolM3)
题目练习
1,[NOIP2001 普及组] 求先序排列 - 洛谷
实现:中序ACGDBHZKX,后序CDGAHXKZB,首先可找到主根B;那么我们找到中序遍历中的B,由这种遍历的性质,可将中序遍历分为ACGD和HZKX两棵子树,那么对应可找到后序遍历CDGA和HXKZ(从头找即可)从而问题就变成求1.中序遍历ACGD,后序遍历CDGA的树 2.中序遍历HZKX,后序遍历HXKZ的树;接着递归,按照原先方法,找到1.子根A,再分为两棵子树2.子根Z,再分为两棵子树。就按这样一直做下去(先输出根,再递归)
AC代码
#include<bits/stdc++.h>
using namespace std;
void f(string in, string post);
int main()
{
string in, post;
cin >> in >> post;
f(in, post);
cout << endl;
return 0;
}
void f(string in, string post)
{
if (in.size() > 0) {
char c = post[post.size() - 1];//后序找根
cout << c;//不断输出根
int k = in.find(c);//在中序中找根,把树分成两边
f(in.substr(0, k), post.substr(0, k));//递归左子树
f(in.substr(k + 1), post.substr(k, in.size() - k - 1));//递归右子树
}
}
2,新二叉树 - 洛谷
实现:以第一个数组为主数组,将其余数组全部续上。最后输出的时候只输出字母即可
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
//以第一个数组为主数组,将其余数组全部续上
int main()
{
int n;
string str;
cin >> n >> str;
for (int i = 2; i <= n; i++) {
string s;
cin >> s;
int x = str.find(s[0]);//找到后续插入的位置
str.erase(x, 1);//从主数组中删除长度为1的子串,起始索引为x。
str.insert(x, s);
}
for (int i = 0; i < str.length(); i++) {
if (str[i] != '*') cout << str[i];
}
return 0;
}
3,遍历问题 - 洛谷
实现:只有一个儿子的节点才会在知道 前序后序 的情况下有不同的中序遍历,所以将题目转化成找只有一个儿子的节点个数。可以很容易的找出这类节点在前序后序中出现的规律。(前序中出现AB,后序中出现BA,则这个节点只有一个儿子)每个这类节点有两种中序遍历(及儿子在左,儿子在右)根据乘法原理中序遍历数为 2^节点个数。
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
//找出只有一个子节点的节点,只有这种情况才会出现多种排序
int main()
{
string a, b;
cin >> a >> b;
int ans = 0;
for (int i = 0; i < a.length() - 1; i++) {//注意取值范围
for (int j = 1; j < b.length(); j++) {
if (a[i] == b[j] && a[i + 1] == b[j - 1]) ans++;
}
}
cout << (1 << ans) << endl;
return 0;
}
4,[NOIP2004 普及组] FBI 树 - 洛谷
题目大意:
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
string s;
// 定义函数 f,用于构造 FBI 树
void f(int l, int r);
int main()
{
int n;
cin >> n >> s;
f(0, (1 << n) - 1); // 调用函数 f,构造 FBI 树并输出后序遍历序列
return 0;
}
void f(int l, int r)
{
if (l < r) { // 如果范围 l 和 r 还需要细分(即范围至少包含两个字符)
f(l, (l + r) / 2); // 细分范围的左半部分,递归调用 f
f((l + r) / 2 + 1, r); // 细分范围的右半部分,递归调用 f
}
int B = 1, I = 1; // 初始化为全0串(B结点)和全1串(I结点)
for (int i = 0; i <= r - l; i++) {
// 遍历范围内的字符,判断是否存在全0串(B结点)或全1串(I结点)
if (s[l + i] == '1') B = 0;
else if (s[l + i] == '0') I = 0;
}
if (B) cout << "B";
else if (I) cout << "I";
else cout << "F";
}
三,深度优先搜索(DFS)
一篇非常好的博客,建议把上面的代码敲一遍(http://t.csdnimg.cn/GFXma)。
题目练习
1,全排列 全排列问题 - 洛谷
思路:不多说了,套模板就行
AC代码
#define _CRT_SECURE_NO_WARNINGS
#include<cstdio>
#include<iostream>
using namespace std;
int book[20],num[20], n;
void dfs(int step);
int main()
{
cin >> n;
dfs(1);
return 0;
}
void dfs(int step)
{
if (step == n + 1) {
for (int i = 1; i <= n; i++) printf("%5d", num[i]);
printf("\n");
return;
}
for (int i = 1; i <= n; i++) {
if (book[i] == 0) {
num[step] = i;
book[i] = 1;
dfs(step + 1);
book[i] = 0;
}
}
return;
}
2,部分排列 万里ACM
思路:在全排列的基础上改一下输出范围就行
AC代码
#define _CRT_SECURE_NO_WARNINGS
#include<cstdio>
#include<iostream>
using namespace std;
int book[20], n, m;
char num[20];
void dfs(int step);
int main()
{
cin >> n >> m;
dfs(1);
return 0;
}
void dfs(int step)
{
if (step == m + 1) {
for (int i = 1; i <= m; i++) printf("%c", num[i]);//改一下输出就行
printf("\n");
return;
}
for (int i = 1; i <= n; i++) {
if (book[i] == 0) {
num[step] = i + 64;
book[i] = 1;
dfs(step + 1);
book[i] = 0;
}
}
return;
}
3,组合输出 组合的输出 - 洛谷
思路:在全排列的基础上加上赋值条件(后面的数比前面大)
AC代码
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
int book[50], n, m;
int num[50], s[50];
void dfs(int step);
int main()
{
cin >> n >> m;
dfs(1);
return 0;
}
void dfs(int step)
{
if (step == m + 1) {
for (int i = 1; i <= m; i++) printf("%3d", num[i]);
printf("\n");
return;
}
for (int i = 1; i <= n; i++) {
if (book[i] == 0) {
if (num[step - 1] < i) {//只有后面的数比前面大,程序才会继续
num[step] = i;
book[i] = 1;
dfs(step + 1);
book[i] = 0;
}
}
}
return;
}
4,有重复元素排列(字母) 万里ACM
思路:使用回溯算法进行递归,尝试将字母放置在每一个位置上。在每一步递归中,判断字母是否可以放置在当前位置,如果可以,则将字母放置在当前位置,递归继续排列下一个位置。
AC代码
#include<bits/stdc++.h>
using namespace std;
char s[1000]; // 定义字符数组s,用于存储待排列的n个小写字母
int n, ans, w[27]; // n表示小写字母的个数,ans表示排列的总数,w数组用于统计每个字母的数量
void dfs(int step);
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> s[i];
w[s[i] - 'a' + 1]++; // 统计每个字母的数量,将字母转换成编号来统计
}
dfs(1);
cout << ans << endl;
return 0;
}
void dfs(int step)
{
if (step == n + 1) {
for (int i = 1; i <= n; i++) printf("%c", s[i] + 96);
printf("\n");
ans++;
return;
}
for (int i = 1; i <= 26; i++) {
if (w[i] > 0) {
// 如果字母i的数量大于0,则可以将字母i放在当前位置step上
s[step] = i; // 将字母i放在当前位置step上
w[i]--; // 将字母i的数量减1
dfs(step + 1); // 继续排列下一个位置
w[i]++; // 回溯,将字母i的数量还原
}
}
return;
}
5,有重复元素排列(数字)
6,求迷宫的总方案数(DFS) 迷宫 - 洛谷
思想:经典的迷宫问题,看代码就行。
AC代码
#include<bits/stdc++.h>
using namespace std;
int a[10][10], book[10][10], ans;
int row, col, x2, y2;
int next_d[4][2] = { {1,0},{-1,0},{0,1},{0,-1} }; // 四个方向的偏移量
void dfs(int x1, int y1);
int main()
{
int n;
int x1, y1;
cin >> row >> col >> n; // 输入迷宫的行数、列数和障碍总数
cin >> x1 >> y1 >> x2 >> y2; // 输入起点坐标和终点坐标
for (int i = 0; i < n; i++) {
int x, y;
cin >> x >> y;
book[x][y] = 1; // 标记障碍点
}
dfs(x1, y1); // 开始搜索从起点到终点的路径
cout << ans << endl; // 输出路径总数
return 0;
}
void dfs(int x1, int y1)
{
if (x1 == x2 && y1 == y2) {
ans++; // 终点坐标,路径总数加1
return;
}
int tx, ty; // 下一个方向的坐标
for (int i = 0; i < 4; i++) {
tx = x1 + next_d[i][0]; // 计算下一个方向的x坐标
ty = y1 + next_d[i][1]; // 计算下一个方向的y坐标
if (tx < 1 || tx > col || ty < 1 || ty > row) continue; // 越界判断
if (book[tx][ty] == 0) {
book[x1][y1] = 1; // 标记当前的点,表示已经访问过
dfs(tx, ty); // 前往下一个点继续搜索
book[x1][y1] = 0; // 回溯,将当前点标记为未访问状态
}
}
return;
}
7,N皇后问题
思想:主要是解决斜对角线如何判断的问题,其他的套模板就行
AC代码
#include <cstdio>
#include <iostream>
using namespace std;
int ans[14]; // 存放每一行皇后所在的列号
int check[3][28] = { 0 }; // 记录每个位置是否已经有皇后,用于判断是否可以放置皇后
int sum = 0; // 记录解的总个数
int n; // 棋盘大小
void eq(int line) {
if (line > n) {
sum++;
if (sum > 3) return; // 如果总数已经超过3,直接返回
else { // 否则输出该解
for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
printf("\n");
return;
}
}
for (int i = 1; i <= n; i++) {
// 判断是否可以放置皇后,并更新check数组
if ((!check[0][i]) && (!check[1][line + i]) && (!check[2][line - i + n])) {
ans[line] = i;
check[0][i] = 1; check[1][line + i] = 1; check[2][line - i + n] = 1;
eq(line + 1); // 继续放置下一行的皇后
check[0][i] = 0; check[1][line + i] = 0; check[2][line - i + n] = 0; // 回溯,撤销该次放置
}
}
}
int main() {
scanf("%d", &n);
eq(1);
printf("%d", sum);
return 0;
}
四,广度优先搜索(BFS)
非常推荐b站一个up主的视频,BFS的原理讲解的十分清晰
题目练习
1,走迷宫最短路径问题
AcWing 844 走迷宫
给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。最初,有一个人位于左上角 (1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。数据保证 (1,1) 处和 (n,m) 处的数字为 0,且一定至少存在一条通路。
输入格式
第一行包含两个整数 n和 m。
接下来 n 行,每行包含 m 个整数(0 或 1),表示完整的二维数组迷宫。
输出格式
输出一个整数,表示从左上角移动至右下角的最少移动次数。
数据范围
1≤n,m≤100
输入样例:
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
输出样例:
8
思想:BFS天生的具有 找最短路径 的特性,所以这题直接套用模板就行
AC代码
#include<iostream>
#include<queue>
using namespace std;
int mp[500][500]; // 存储迷宫的地图信息
int n, m; // 迷宫的行数和列数
int next_d[4][2] = { {1,0},{-1,0},{0,1},{0,-1} }; // 定义四个方向的偏移量
struct pos {
int x, y, cnt; // 记录坐标和步数
pos(int a = 0, int b = 0, int c = 0) : x(a), y(b), cnt(c) {} // 初始化结构体
};
void bfs(void); // 定义BFS函数
int main()
{
cin >> n >> m; // 输入迷宫的行数和列数
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> mp[i][j]; // 输入迷宫地图信息
}
}
bfs(); // 调用BFS函数求解最短路径
return 0;
}
void bfs(void)
{
queue<pos> q;
q.push({ 1,1 }); // 将起点入队
mp[1][1] = 1; // 标记起点为已访问
while (!q.empty()) { // 当队列不为空时,继续搜索
struct pos p = q.front(); // 取出队首元素
q.pop();
if (p.x == n && p.y == m) { // 如果当前坐标是终点,输出步数并返回
cout << p.cnt << endl;
return;
}
for (int i = 0; i < 4; i++) { // 遍历四个方向
int tx = p.x + next_d[i][0]; // 计算新的横坐标
int ty = p.y + next_d[i][1]; // 计算新的纵坐标
if (tx < 1 || tx > n || ty < 1 || ty > m) continue; // 如果新坐标越界,跳过
if (mp[tx][ty] == 0) { // 如果新坐标是可通过的,入队并标记为已访问
q.push({ tx, ty, p.cnt + 1 });
mp[tx][ty] = 1;
}
}
}
}
2.数黑砖 https://vjudge.net/contest/572876#problem/I
思想:也是一样套模板,不同的是每次找到合适的路径后 cnt+1.
AC代码
#include<iostream>
#include<queue>
using namespace std;
char a[500][500]; // 存储迷宫的地图信息
int m, n; // 迷宫的列数和行数
int next_d[4][2] = { {1,0},{-1,0},{0,1},{0,-1} }; // 定义四个方向的偏移量
struct pos {
int x, y; // 记录坐标
pos(int a = 0, int b = 0) : x(a), y(b) {} // 初始化结构体
};
int bfs(pos st); // 定义BFS函数
int main()
{
struct pos st;
while (cin >> m >> n) { // 循环读入多个迷宫
if (m == 0 && n == 0) break; // 如果行数和列数都为0,结束循环
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j]; // 输入迷宫地图信息
if (a[i][j] == '@') { // 记录起点的坐标
st.x = i, st.y = j;
}
}
}
cout << bfs(st) << endl; // 调用BFS函数,输出可达到的格子个数
}
return 0;
}
int bfs(pos st)
{
int cnt = 1; // 可达到的格子个数,初始为1,因为起点也算一个格子
queue<pos> q;
q.push(st); // 将起点入队
a[st.x][st.y] = '#'; // 标记起点为已访问
while (!q.empty()) { // 当队列不为空时,继续搜索
struct pos p = q.front(); // 取出队首元素
q.pop();
for (int i = 0; i < 4; i++) { // 遍历四个方向
int tx = p.x + next_d[i][0]; // 计算新的横坐标
int ty = p.y + next_d[i][1]; // 计算新的纵坐标
if (tx < 1 || tx > n || ty < 1 || ty > m) continue; // 如果新坐标越界,跳过
if (a[tx][ty] == '.') { // 如果新坐标是可通过的,入队并标记为已访问
cnt++; //找到合适的路径cnt + 1
q.push({ tx, ty });
a[tx][ty] = '#';
}
}
}
return cnt; // 返回可达到的格子个数
}
3.马的遍历 马的遍历 - 洛谷
思想:其实就是方向不一样了,改成了8个方向,其本质和数黑砖类似
AC代码
#include<bits/stdc++.h>
using namespace std;
int n, m;
int mp[500][500];
int dir[8][2] = { {2,-1},{2,1},{-2,1},{-2,-1},{1,2},{-1,2},{1,-2},{-1,-2} };
struct pos {
int r, c, d;
pos(int a = 0, int b = 0, int d = 0) :r(a), c(b), d(d) {}
};
void bfs(pos st);
int main()
{
int sr, sc;
cin >> n >> m >> sr >> sc;
memset(mp, -1, sizeof mp);
pos st;
st.r = sr, st.c = sc;
bfs(st);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cout << mp[i][j] << '\t';
}
cout << endl;
}
return 0;
}
void bfs(pos st)
{
queue<pos>q;
q.push(st);
mp[st.r][st.c] = 0;//初始化起点
while (!q.empty()) {
pos p1 = q.front();
q.pop();
for (int i = 0; i < 8; i++) {
int tr = p1.r + dir[i][0];
int tc = p1.c + dir[i][1];
int td = p1.d + 1;
if (tr<1 || tr>n || tc<1 || tc>m) continue;
if (mp[tr][tc] == -1) {
mp[tr][tc] = td;//当前位置步数
q.push(pos(tr, tc, td));//当前位置入队
}
}
}
return;
}
4.奇怪电梯 奇怪的电梯 - 洛谷
思想:两个方向,然后输出只需要最短步数(也就是最短路径),BFS套模板就行
AC代码
#include<bits/stdc++.h>
using namespace std;
int n, a, b;
int fl[1000], flag[1000];
struct pos {
int floor;
int cnt;
pos(int a = 0, int b = 0) :floor(a), cnt(b) {}
};
void bfs();
int main()
{
cin >> n >> a >> b;
for (int i = 1; i <= n; i++) cin >> fl[i];
bfs();
return 0;
}
void bfs()
{
queue<pos> q;
q.push({ a,0 });
flag[a] = 1;
while (q.size()) {
pos p1 = q.front();
q.pop();
if (p1.floor == b) {//第一个到达终点的就是最短路径
cout << p1.cnt << endl;
return;
}
for (int i = 0; i < 2; i++) {
int floor = p1.floor + fl[p1.floor] * pow(-1, i);
if (floor<1 || floor>n) continue;
if (flag[floor] == 0) {
flag[floor] = 1;
q.push({ floor,p1.cnt + 1 });
}
}
}
cout << "-1";//无解
}
5.湖的数量 [USACO10OCT] Lake Counting S - 洛谷
思想:本题的就是8个方向的走迷宫,还是一样,套模板!
AC代码
#include<bits/stdc++.h>
using namespace std;
char mp[200][200];
int next_d[8][2] = { {1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1} };
int ans, n, m;
struct pos {
int x, y;
pos(int a = 0, int b = 0) :x(a), y(b) {} // 初始化结构体
};
void bfs(pos p);
int main()
{
cin >> n >> m; // 输入行数和列数
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> mp[i][j]; // 输入迷宫地图信息
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (mp[i][j] == 'W') { // 如果遇到水坑起点,进入BFS搜索
struct pos p;
p.x = i, p.y = j; // 起点坐标
bfs(p); // 进行BFS搜索
ans++; // 搜索完一个水坑,答案加1
}
}
}
cout << ans << endl; // 输出结果
return 0;
}
void bfs(pos p)
{
queue<pos> q;
q.push(p); // 将起点入队
mp[p.x][p.y] = '.'; // 标记起点为已访问
while (!q.empty()) { // 当队列不为空时,继续搜索
struct pos p1 = q.front(); // 取出队首元素
q.pop();
for (int i = 0; i < 8; i++) { // 遍历8个方向
int tx = p1.x + next_d[i][0]; // 计算新的横坐标
int ty = p1.y + next_d[i][1]; // 计算新的纵坐标
if (tx < 1 || tx > n || ty < 1 || ty > m) continue; // 如果新坐标越界,跳过
if (mp[tx][ty] == 'W') { // 如果新坐标是水坑的一部分,入队并标记为已访问
q.push({ tx, ty });
mp[tx][ty] = '.';
}
}
}
return;
}
6.填涂颜色 填涂颜色 - 洛谷
思想:其实本题的难点是如何分清圈内和圈外,先认为所有的0都是2,然后把不是的去除。也就是把由边界和1围成的2全部改成0.
AC代码
#include<bits/stdc++.h>
using namespace std;
int mp[50][50], n; // 方阵大小n和方阵数组
int d[4][2] = { {1,0},{-1,0},{0,-1},{0,1} }; // 上下左右四个方向的坐标变化
struct pos {
int x, y;
pos(int a = 0, int b = 0) :x(a), y(b) {} // 结构体初始化
};
void change(void); // 找到起点,确定闭合圈的边界
void bfs(pos st); // 使用BFS遍历闭合圈内的点,并填充为0
int main()
{
cin >> n; // 输入方阵大小
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> mp[i][j]; // 输入方阵信息
if (mp[i][j] == 0) mp[i][j] = 2; // 将所有0标记为2
}
}
change(); // 找到起点,确定闭合圈的边界
// 输出结果
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cout << mp[i][j]; // 输出方阵信息
if (j != n) cout << " ";
}
cout << endl;
}
return 0;
}
void change()
{
// 遍历第一行和最后一行
for (int j = 1; j <= n; j++) {
if (mp[1][j] == 2) { // 如果遇到2,表示可能是闭合圈的起点,进入BFS搜索
struct pos st;
st.x = 1, st.y = j; // 起点坐标
bfs(st); // 进行BFS搜索
}
if (mp[n][j] == 2) { // 同理,遍历最后一行
struct pos st;
st.x = n, st.y = j;
bfs(st);
}
}
// 遍历第一列和最后一列
for (int i = 1; i <= n; i++) {
if (mp[i][1] == 2) { // 如果遇到2,表示可能是闭合圈的起点,进入BFS搜索
struct pos st;
st.x = i, st.y = 1; // 起点坐标
bfs(st); // 进行BFS搜索
}
if (mp[i][n] == 2) { // 同理,遍历最后一列
struct pos st;
st.x = i, st.y = n;
bfs(st);
}
}
}
void bfs(pos st)
{
queue<pos> q;
q.push({ st.x,st.y }); // 将起点入队
mp[st.x][st.y] = 0; // 将起点标记为0,表示已访问
while (!q.empty()) { // 当队列不为空时,继续搜索
struct pos p = q.front(); // 取出队首元素
q.pop();
for (int i = 0; i < 4; i++) { // 遍历四个方向
int tx = p.x + d[i][0]; // 计算新的横坐标
int ty = p.y + d[i][1]; // 计算新的纵坐标
if (tx < 1 || tx > n || ty < 1 || ty > n) continue; // 如果新坐标越界,跳过
if (mp[tx][ty] == 2) { // 如果新坐标是闭合圈的一部分,入队并标记为0,表示已访问
mp[tx][ty] = 0;
q.push({ tx, ty });
}
}
}
return;
}
五,搜索的综合问题
这一章涉及到了很多别的算法,一开始可能有点难理解。
1,最短路计数 最短路计数 - 洛谷
关于最短路径的问题的一些算法,可以看看b站的一个视频,讲的很全面
(最短路径算法全套(floyed+dijstra+Bellman+SPFA)_哔哩哔哩_bilibili)
思想:这题是双向,且权值都为1的最短路径计数问题。本代码用BFS和由vector构建的图来解决此问题的。
AC代码
#include<bits/stdc++.h>
using namespace std;
const int MOD = 100003;
vector<int> bfs(vector<vector<int>>& mp, int start);
int main()
{
int n, m;
cin >> n >> m;//n个点,m条路径
vector<vector<int>> mp(n + 1);//二维数组,构建图
for (int i = 0; i < m; i++) {
int x, y;
cin >> x >> y;
mp[x].push_back(y);
mp[y].push_back(x);//无向图
}
vector<int> cnt = bfs(mp, 1);//从节点1开始,返回每个起点1到每个节点的最短路径,用cnt保存
for (int i = 1; i <= n; i++) cout << cnt[i] << endl;//输出
return 0;
}
vector<int> bfs(vector<vector<int>>& mp, int start)
{
int n = mp.size();
vector<int> distance(n, INT_MAX);//保存起点到每个点的最短路径,初始化为无穷大
vector<int> cnt(n, 0);//保存起点到每个点最短路径的条数,初始化为0
distance[start] = 0;//起点到起点的最短路径是0
cnt[start] = 1;//起点到起点只有一条路径
queue<int>q;
q.push(start);
while (q.size()) {
int node = q.front();
q.pop();
for (int i = 0; i < mp[node].size(); i++) {//遍历当前的邻居节点
int neighbor = mp[node][i];
if (distance[neighbor] == INT_MAX) {
// 如果邻居顶点还没有被访问过,更新其距离和最短路径条数
distance[neighbor] = distance[node] + 1;
cnt[neighbor] = cnt[node];
q.push(neighbor);
}
else if (distance[neighbor] == distance[node] + 1) {
// 如果存在通过当前顶点到达邻居顶点的另一条最短路径
// 更新到达邻居顶点的最短路径条数
cnt[neighbor] = (cnt[node] + cnt[neighbor]) % MOD;// 将当前顶点的路径条数加到邻居顶点的路径条数上
}
}
}
return cnt;
}
2,八数码问题 https://www.luogu.com.cn/problem/P1379
思想:也是用BFS来写,把空格当作起点,每个方向进行一次交换(后面要还原),并使用了map里的count函数用于去重。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int Target = 123804765;
int d[4][2] = { {1,0},{-1,0},{0,1},{0,-1} };
int bfs(LL num);
int main()
{
LL num;
cin >> num;
cout << bfs(num) << endl;
return 0;
}
int bfs(LL num)
{
queue<LL>q;
q.push(num);
map<LL, LL> m; //保存步数
m[num] = 0;
while (!q.empty()) {
LL p = q.front();
q.pop();
int board[3][3], x, y;
LL t = p;
if (p == Target) return m[Target];
//将数列变为九宫格,并保存空格
for (int i = 2; i >= 0; i--) {
for (int j = 2; j >= 0; j--) {
board[i][j] = t % 10;
t /= 10;
if (board[i][j] == 0) {
x = i, y = j;
}
}
}
// 以空格为起点,对四个方向进行移动
for (int i = 0; i < 4; i++) {
int tx = x + d[i][0];
int ty = y + d[i][1];
if (tx < 0 || tx > 2 || ty < 0 || ty > 2) continue; // Fix the typo here (tx > 2)
swap(board[tx][ty], board[x][y]);
//将九宫格变为数组,并存入队列
LL w = 0;
for (int r = 0; r < 3; r++) {
for (int c = 0; c < 3; c++) {
w = w * 10 + board[r][c];
}
}
if (!m.count(w)) { //使用了C++的map容器的count函数,用于判断map中是否已经包含ns,用于去重
m[w] = m[p] + 1;
q.push(w);
}
swap(board[tx][ty], board[x][y]);//还原
}
}
return m[Target];
}
3,打开所有的灯 打开所有的灯 - 洛谷
思想:刚开始本来想用BFS写,但是自己写的超时,别人的不好理解。只能用比较好理解的DFS来写了(哭)。
AC代码
#include<bits/stdc++.h>
using namespace std;
int mp[10][10], flag[10][10], ans = 9999; // 存储初始灯状态、标记是否点击、保存最少步数的变量
int d[5][2] = { {1,0},{-1,0},{0,1},{0,-1},{0,0} }; // 方向数组,用于改变灯的状态
void dfs(int step); // 深度优先搜索函数声明
bool check(); // 检查是否所有灯都打开
void change(int x, int y); // 改变灯的状态函数
int main()
{
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
cin >> mp[i][j]; // 输入初始灯状态
}
}
dfs(0); // 开始深度优先搜索,初始步数为0
cout << ans << endl; // 输出最少步数
return 0;
}
// 深度优先搜索函数,step表示当前步数
void dfs(int step)
{
if (check()) ans = min(ans, step); // 如果所有灯都打开了,更新最少步数
// 遍历每一个灯的状态
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (flag[i][j] == 0) { // 如果灯没有被点击过
flag[i][j] = 1; // 标记当前灯已点击
change(i, j); // 改变当前灯的状态以及周围灯的状态
dfs(step + 1); // 递归搜索下一步
// 回溯,恢复状态
flag[i][j] = 0;
change(i, j);
}
}
}
return;
}
// 检查是否所有灯都打开
bool check()
{
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (mp[i][j] == 0) return 0; // 如果有灯没打开,返回false
}
}
return 1; // 所有灯都打开了,返回true
}
// 改变灯的状态
void change(int x, int y)
{
for (int i = 0; i < 5; i++) {
int tx = x + d[i][0];
int ty = y + d[i][1];
if (mp[tx][ty] == 0) mp[tx][ty] = 1; // 如果是关的,变成开
else mp[tx][ty] = 0; // 如果是开的,变成关
}
return;
}