【题目/训练】回溯、搜索练习1

✨                                                      风起于青萍之末,浪成于微澜之间      🌏 

📃个人主页island1314

🔥个人专栏:算法训练

🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏  💞 💞 💞


一、DFS

 

1. 八皇后

二进制来表示。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <unordered_map>
using namespace std;

int n;
#define MASK(n) ((1<<(n+1))-2) //如 6 得到的是1000 0000 -> 111 1110,零位上的1不用
unordered_map<int, int> ind;

int tot = 3;
int arr[20];
void out()
{
	for (int i = 1; i <= n; i++) {
		if (i > 1) cout << " ";
		printf("%d", arr[i]);
	}
	printf("\n");
	tot--;
	return;
}

int dfs(int i, int t1, int t2, int t3) 
{
	if (i > n) {
		if(tot) out();
		return 1;
	}
	int ans = 0;
	for (int t = t1; t; t -= (-t & t)) {
		int j = ind[-t & t];
		if ((t2 & (1 << (i + j - 1))) && (t3 & (1 << (i - j + n)))) //正斜线和反斜线
		{
			arr[i] = j;
			ans += dfs(i + 1, t1^(1 << j), t2^(1 << (i + j - 1)), t3^(1 << (i - j + n))); //把t1中的j标记为0,然后向左移动一位
		}
	}
	return ans;
}


int main()
{
	scanf("%d", &n);
	for (int i = 0; i < 2 * n; i++) ind[1 << i] = i;
	int ans = dfs(1, MASK(n), MASK(2 * n - 1), MASK(2 * n - 1)); //列,正斜边,反斜边
	cout << ans << "\n";
	return 0;
}

2. 奇怪的电梯

思路

先设立两个数组dis[]和to[],分别表示到某层的最少按钮数和按键,dfs(k , a)中k表示使用的按钮数目,a表示到某一层,当dis[]记录到的到某层的最少按钮数小于等于k时,就可递归返回。

比如对于

5 1 5
3 3 1 2 5

进行如下优化

#include <iostream>
using namespace std;

const int N = 205;
int to[N];
int dis[N]; //起点到每个点最短距离
int n;

void dfs(int k, int a)
{
	if (dis[a] <= k) return;
	dis[a] = k; //刷新到a的最短距离
	
	if (a + to[a] <= n) dfs(k + 1, a + to[a]);
	if (a - to[a] >= 1) dfs(k + 1, a - to[a]);
}


int main()
{
	int  a, b;
	cin >> n >> a >> b;
	for (int i = 1; i <= n; i++) {
		cin >> to[i]; //输入每个按钮的值
		dis[i] = n + 1;
	}
	dfs(0, a);
	printf("%d\n", dis[b] == n + 1 ? -1 : dis[b]);

	return 0;
}

3. 选数

dfs(u,ind,sum)分别表示当前已经选择了几个数,当前这一层可以选择的最小数字,所选当前的和值,is_prime()函数则用来判断是否为质数,这题与之前文章里的【题目/算法训练】全排列相关问题(不用next-permutation)中的组合型枚举很像,想了解的朋友们可以下。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N = 25;
int a[N];
int n, k;
long long ans = 0; //计算素数的数量

bool is_prime(int x)
{
	if (x < 2) return false;
	for (int i = 2; i <= x / i; i++)
	{
		if (x % i == 0) return false;
	}
	return true;
}

void dfs(int u,int ind, int sum)
{
	if (u == k) {
		if (is_prime(sum)) ans++;
		return;
	}
	for(int i = ind; i <= n; i++)
	{
		dfs(u + 1, i + 1,sum + a[i]);
	}
}


int main()
{
	cin >> n >> k;
	for (int i = 1; i <= n; i++) cin >> a[i];
	dfs(0, 1, 0); 
	printf("%lld\n", ans);
	return 0;
}

4. 迷宫

00000
01110
01110
01110
00000

技巧:从(1,1)开始读入,给地图上可以行走的地方初始化为1,0表示不可以走的点,相当于给地图外面放上障碍,就不用向前面几题一样对(x,y)进行特殊判断(如判断是否为1该点相邻的点,是不是障碍,有没有访问过)

#include <iostream>
using namespace std;

const int N = 7;
int g[N][N];
int n, m, t, sx, sy, ex, ey;
int ans = 0; //记录方法的数量

int dir[4][2] = {
	{0,1},{1,0},{0,-1},{-1,0}
};

void dfs(int x, int y)
{
	if (x == ex && y == ey) {
		ans++;
		return;
	}
	g[x][y] = 0;//表示当前点遍历过
	for (int i = 0; i < 4; i++) {
		int dx = x + dir[i][0], dy = y + dir[i][1];
		if (g[dx][dy] == 0)  continue;
		dfs(dx, dy);
	}
	g[x][y] = 1;
}

int main()
{
	cin >> n >> m >> t;
	for (int i = 1; i <= n; i++){
		for(int j =1;j<=m;j++) g[i][j] = 1;
	}
	cin >> sx >> sy >> ex >> ey;
	for (int i = 0; i < t; i++) {
		int x, y;
		cin >> x >> y;
		g[x][y] = 0;
	}
	dfs(sx, sy);
	cout << ans << "\n";
	return 0;
}

5. 吃奶酪

#include <iostream>
#include <cmath>
using namespace std;

const int N = 17;
double ans = 1e9;
int n;
double x[N], y[N];
double dis[N][N]; //dis[i][j]表示从第i个奶酪到第j个奶酪的距离
double dp[70000][20]; //2^16,第一个位置表示状态编码,第二个位置表示达到当前状态,最后一个吃掉的奶酪编号
int ind[70000]; //权值映射

double d(int i, int j) {
	return sqrt((x[i] - x[j])*(x[i] - x[j]) + (y[i] - y[j])* (y[i] - y[j]));
}

void dfs(int t, int now, double s) {
	if (t == 0) { //吃掉了所有奶酪
		if (s < ans) ans = s;
		return;
	}
	for (int k = t; k; k -= (k & -k)) {
		int to = ind[k & -k], next_t = t - (1 << to);
		double l = s + dis[now][to];
		if (dp[next_t][to] != 0 && dp[next_t][to] <= l) continue;
		
		
		dp[next_t][to] = l;
		if (ans <= l) continue;

		dfs(next_t, to, l);
	}
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	x[0] = y[0] = 0; //小老鼠位置
	for (int i = 1; i <= n; i++) cin >> x[i] >> y[i];

	for (int i = 0; i <= n; i++)
	{
		for (int j = i; j <= n; j++) 
			dis[i][j] = dis[j][i] = d(i, j);
	}
	for (int k = 1, i = 0; i <= N; i++, k *= 2)ind[k] = i; //权值到下标映射
	dfs((1 << (n + 1)) - 2, 0, 0); //当前位置状态码,所在奶酪下标,到达状态所走总路程
	printf("%.2lf\n", ans);
	return 0;
}

6. 小红走矩阵

输入:

3
3 2 1
6 5 4
9 8 7

输出:

7

#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 505;
int g[N][N];
int n;
bool vis[N][N]; //判断是否走过

int dir[4][2] = {
	{0,1},{1,0},{0,-1},{-1,0}
};

void dfs(int x, int y,int z)
{
	if (g[x][y] > z || vis[x][y] || vis[n][n]) return;
	vis[x][y] = 1;
	for (int i = 0; i < 4; i++) {
		int dx = x + dir[i][0], dy = dir[i][1] + y;
		if (dx < 1 || dx > n || dy < 1 || dy >n || vis[dx][dy]) continue;
		dfs(dx, dy, z);
	}
}

int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			cin >> g[i][j];
		}
	}
	int  l = 1, r = 1e9, ans = 1e9, md;
	while (l <= r) {
		md = (l + r) >> 1;
		memset(vis, 0, sizeof(vis));
		dfs(1, 1, md);
		if (vis[n][n]) {
			ans = min(ans, md);
			r = md - 1;
		}
		else l = md + 1;
	}
	cout << ans;

	return 0;
}

7. 单词搜索

很常规的DFS搜索,当第一个字符发生匹配时,就开始往下搜索,不匹配就退回来,直到遍历完所有字符都没有匹配成功,才返回false,因此需要注意的是:应该是写成if (dfs(board, word, i, j, 0)) return true;,而不是return dfs(board, word, i, j, 0);

class Solution {
public:
    int dir[4][2] = {
        {1,0},{0,1},{-1,0},{0,-1}
    };
    int n, m;
    bool st[505][505] = { false }; //判断该字符是否使用过,未用过标记未false
    bool exist(vector<string>& board, string word) {
        n = board.size();
        m = board[0].size();
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < m; j++)
            {
                if (board[i][j] == word[0])
                {
                    if (dfs(board, word, i, j, 0)) return true;
                }
            }
        }
        return false;
    }
    bool dfs(vector<string>& board, string word, int x, int y, int index) {
        if (index == word.size() - 1) return true;
        st[x][y] = true;
        for (int i = 0; i < 4; i++)
        {
            int dx = x + dir[i][0], dy = y + dir[i][1];
            if (dx < 0 || dx >= n) continue;
            if (dy < 0 || dy >= m) continue;
            if (board[dx][dy] != word[index + 1] || st[dx][dy]) continue;
            if (dfs(board, word, dx, dy, index + 1)) return true;
        }
        st[x][y] = false;
        return false;
    }
};

二、BFS

1.  走迷宫

思路:

一道很常规的搜索问题,由于要求到终点的最短路径,那么我BFS的方法即可

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1005;

char grid[N][N];
bool vis[N][N] = {false}; //记录是否遍历故该点
int w[N][N]; //记录到某个点的路径长度

int dir[4][2] = {
	{-1,0},{1,0},{0,-1},{0,1}
};

// 走迷宫
void solve2()
{
	int n, m;
	cin >> n >> m;
	int sx, sy, tx, ty;
	cin >> sx >> sy >> tx >> ty;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> grid[i][j];
		}
	}

	memset(w, -1, sizeof(w)); //初始为 -1,表面每个位置都没有搜索过
	vis[sx][sy] = true;
	queue < pair<int, int>> q;
	q.push(make_pair(sx, sy)); //入起始位置
	while (!q.empty()) 
	{
		// 从当前位置开始遍历
		int i = q.front().first, j = q.front().second;
		q.pop();
		for (int k = 0; k < 4; k++) 
		{
			int x = i + dir[k][0], y = j + dir[k][1];
			if (x > 0 && x <= n && y > 0 && y <= m && !vis[x][y] && grid[x][y] == '.')
			{
				vis[x][y] = true;
				w[x][y] = w[i][j] + 1;
				q.push(make_pair(x, y));
			}
		}
	}

	int d = w[tx][ty] + 1; 

	if (d == 0) cout << -1 << endl;
	else cout << d << endl;
}

int main()
{
	solve2();
}

 2. 马的遍历

思路:

   假设马在(x,y)这个点,则马可以移动的方向有8个,偏移量如下所示,注意马走日。

注:马一次跳跃到达的点(x1,y1)和马原坐标(x,y)的关系是 ∣x1−x∣+∣y1−y∣=3.

(-1,-2)(1,-2)
(-2,-1)(2,-1)
(x,y)
(-2,1)(2,1)
(-1,2)(1,2)
#include <iostream>
using namespace std;
const int N = 405;

int dis[N][N];
int n, m;
int dir[8][2] = { //偏移量
	{2,1},{-2,1},{2,-1},{-2,-1},
	{1,2},{1,-2}, {-1,2},{-1,-2}
};

void dfs(int step, int x, int y)
{
	if (dis[x][y] != -1 && dis[x][y] <= step) return;
	dis[x][y] = step;
	for (int k = 0; k < 8; k++) {
		int dx = x + dir[k][0], dy = y + dir[k][1]; 
		if (dx<1 || dx>n) continue;
		if (dy<1 || dy>m) continue;
		dfs(step + 1, dx, dy);
	}
}


int main()
{

	int a, b;
	cin >> n >> m >> a >> b;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			dis[i][j] = -1; //赋初值
		}
	}
	dfs(0, a, b); //到达当前点所花步数,(a,b)表示当前所在位置

	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cout << dis[i][j] << " ";
		}
		cout << "\n";
	}

	return 0;
}

这题如果用上面的dfs(step,x,y)来表示则会超时,因此我们应该使用BFS算法层序遍历,遍历每一层,如果遍历了某个节点时,那么后续遍历这个节点绝对比之前找的step要大,层序遍历更适合求最短步长

#include <iostream>
#include <queue>
using namespace std;
const int N = 405;
int dir[8][2] = { //偏移量
	{2,1},{-2,1},{2,-1},{-2,-1},
	{1,2},{1,-2}, {-1,2},{-1,-2}
};

int dis[N][N];
int n, m;
struct Node { //广搜队列
	Node(int x, int y, int s) :x(x), y(y), s(s) {}
	int x, y, s;
};

void bfs(int a, int b){
	queue<Node> q;
	q.push(Node(a, b, 0));
	dis[a][b] = 0;

	//从当前节点扩展到其他点
	while (!q.empty()) {
		Node now = q.front();
		q.pop();
		for (int k = 0; k < 8; k++) {
			int dx = now.x + dir[k][0], dy = now.y + dir[k][1];
			if (dx < 1 || dx > n) continue;
			if (dy < 1 || dy > m) continue;
			if (dis[dx][dy] != -1)continue;
			q.push(Node(dx, dy, now.s + 1));
			dis[dx][dy] = now.s + 1;
		}
	}
}

int main()
{
	int a, b;
	cin >> n >> m >> a >> b;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			dis[i][j] = -1; //赋初值
		}
	}
	bfs(a, b); //到达当前点所花步数,(a,b)表示当前所在位置

	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cout << dis[i][j] << " ";
		}
		cout << "\n";
	}
	return 0;
}


评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值