YF“愉快”的一周(第四周)

目录

一,贪心

 题目练习  CSDN

二,二叉树

题目练习

1,[NOIP2001 普及组] 求先序排列 - 洛谷

2,新二叉树 - 洛谷

3,遍历问题 - 洛谷

4,[NOIP2004 普及组] FBI 树 - 洛谷

三,深度优先搜索(DFS)

题目练习

1,全排列   全排列问题 - 洛谷

2,部分排列    万里ACM

3,组合输出   组合的输出 - 洛谷

4,有重复元素排列(字母) 万里ACM

5,有重复元素排列(数字)

6,求迷宫的总方案数(DFS)    迷宫 - 洛谷

7,N皇后问题  

四,广度优先搜索(BFS)

题目练习

1,走迷宫最短路径问题 

2.数黑砖   https://vjudge.net/contest/572876#problem/I

3.马的遍历  马的遍历 - 洛谷

4.奇怪电梯   奇怪的电梯 - 洛谷

5.湖的数量   [USACO10OCT] Lake Counting S - 洛谷

6.填涂颜色  填涂颜色 - 洛谷

五,搜索的综合问题

1,最短路计数   最短路计数 - 洛谷

2,八数码问题    https://www.luogu.com.cn/problem/P1379

3,打开所有的灯  打开所有的灯 - 洛谷


一,贪心

 题目练习  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的原理讲解的十分清晰

BFS广搜解决迷宫问题_哔哩哔哩_bilibili

题目练习

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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值