搜索
DFS三大经典问题
题目:
从 1∼n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。 输入格式 输入一个整数 n。 输出格式 每行输出一种方案。 同一行内的数必须升序排列,相邻两个数用恰好 1 个空格隔开。 对于没有选任何数的方案,输出空行。 本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。 数据范围 1≤n≤15 输入样例: 3 输出样例: 3 2 2 3 1 1 3 1 2 1 2 3
分析:
递归实现指数型枚举: 所实现的功能就是,面对当前数的时候,做出了两种选择,选或不选。 而且,我们是从前往后来的,具有顺序性(升序筛选),所得的序列都是递增的。 选和不选,当前数都已经走过了,不会重复第二次面临选择的情况
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
int n;
vector<int> v;
void dfs(int u)
{
if (u > n)
{
for (auto it : v)
cout << it << ' ';
cout << endl;
return;
}
dfs(u + 1);
v.push_back(u);
dfs(u + 1);
v.pop_back();
}
int main()
{
cin >> n;
dfs(1);
return 0;
}
题目:
从 1∼n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。 输入格式 两个整数 n,m ,在同一行用空格隔开。 输出格式 按照从小到大的顺序输出所有方案,每行 1 个。 首先,同一行内的数升序排列,相邻两个数用一个空格隔开。 其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如 1 3 5 7 排在 1 3 6 8 前面)。 数据范围 n>0 , 0≤m≤n , n+(n−m)≤25 输入样例: 5 3 输出样例: 1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5 思考题:如果要求使用非递归方法,该怎么做呢?
分析:
这个题,和上个题差距不大,只是多了一个长度限制。 不过,我们可以在传参的时候,在搜索状态空间的时候,多加一个状态,就是start,起始位置。
代码1:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
int n, k;
vector<int> v;
void dfs(int u)
{
if (u > n)
{
if (v.size() != k) return;
for (auto it : v)
cout << it << ' ';
cout << endl;
return;
}
v.push_back(u);
dfs(u + 1);
v.pop_back();
dfs(u + 1);
}
int main()
{
cin >> n >> k;
dfs(1);
return 0;
}
代码2:
#include <iostream>
#include <vector>
using namespace std;
int n, k;
vector<int> v;
void dfs(int u, int start)
{
if (u == k)
{
for (int i = 0; i < v.size(); i ++ )
cout << v[i] << " " ;
cout << endl;
return;
}
for (int i = start + 1; i <= n; i ++ )
{
v.push_back(i);
dfs(u + 1, i);
v.pop_back();
}
}
int main()
{
cin >> n >> k;
dfs(0, 0);
return 0;
}
题目:
把 1∼n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。 输入格式 一个整数 n。 输出格式 按照从小到大的顺序输出所有方案,每行 1 个。 首先,同一行相邻两个数用一个空格隔开。 其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。 数据范围 1≤n≤9 输入样例: 3 输出样例: 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1
分析:
递归实现排列型枚举
当前数选过后,对于本递归搜索子树来说,不需要在此使用,但是对于其它子树来说,却需要重新使用。
代码:
#include <iostream>
#include <vector>
using namespace std;
int n;
vector<int> v;
bool st[10];
void dfs(int u)
{
if (u == n)
{
for (int x : v)
cout << x << ' ';
cout << endl;
return;
}
for (int i = 1; i <= n; i ++ )
if (!st[i])
{
st[i] = true;
v.push_back(i);
dfs(u + 1);
v.pop_back();
st[i] = false;
}
}
int main()
{
cin >> n;
dfs(0);
return 0;
}
DFS
DFS大致可以分为两种,一种内部搜索,另一种为外部搜索。
内部搜索,就像是在期盼内部搜索,寻找一条通路、或者搜索有多少个格子之类的,它在格子内部搜索,而且搜索过后,当前各点的状态是不会再发生变化的。
外部搜索,一般为方案数,需要棋盘自身的状态发生变化,每一个格点都有可能发生变化,从而会对全局产生影响。这个时
候,为了解决一个格点对应多状态的情况,需要恢复现场的操作。
(1):分排列型枚举(先后顺序对结果有影响)、组合型枚举(对先后顺序没影响,如集合元素)
如果是排列型枚举:
如果是组合型枚举:可以在dfs内,加一个start,表示从第几个数开始搜索
外部搜索:
题目:
n− 皇后问题是指将 n 个皇后放在 n×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。 1_597ec77c49-8-queens.png 现在给定整数 n,请你输出所有的满足条件的棋子摆法。 输入格式 共一行,包含整数 n。 输出格式 每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。 其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。 每个方案输出完成后,输出一个空行。 注意:行末不能有多余空格。 输出方案的顺序任意,只要不重复且没有遗漏即可。 数据范围 1≤n≤9 输入样例: 4 输出样例: .Q.. ...Q Q... ..Q. ..Q. Q... ...Q .Q..
代码:
#include <cstdio>
using namespace std;
const int N = 10;
int n, k;
char g[N][N];
int col[N], dg[N], udg[N];
void dfs(int u)
{
if (u == n)//当u=n的时候,说明前面从0到n-1这n行都排列好了,那就可以输出方案了
{
for (int i = 0; i < n; i ++ ) printf("%s\n", g[i]);
puts("");
return;
}
for (int i = 0; i < n; i ++ )
if (!col[i] && !dg[i + u] && !udg[u - i + n]) //如果当前行,列,两个斜线都没有皇后
{
col[i] = dg[i + u] = udg[u - i + n] = true;
g[u][i] = 'Q';
dfs(u + 1);
//恢复现场
g[u][i] = '.';
col[i] = dg[i + u] = udg[u - i + n] = false;
}
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
g[i][j] = '.';
dfs(0);
return 0;
}
题目:
棋盘问题 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 109089 Accepted: 49235 Description 在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。 Input 输入含有多组测试数据。 每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n 当为-1 -1时表示输入结束。 随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。 Output 对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。 Sample Input 2 1 #. .# 4 4 ...# ..#. .#.. #... -1 -1 Sample Output 2 1
分析:
和前面的八皇后问题一婶的。甚至更简单了。
代码:
#include <cstdio>
using namespace std;
const int N = 10;
int n, k;
char g[N][N];
bool col[N];
int ans;
void dfs(int u, int cnt)
{
if (u > n) return;//已经搜了n层,结束了
if (cnt == k)
{
ans ++;
return;
}
//不在这一层选,直接进入下一层
dfs(u + 1, cnt);
for (int i = 0; i < n; i ++ )//枚举u这一行的每一列
if (!col[i] && g[u][i] == '#')
{
col[i] = true;
dfs(u + 1, cnt + 1);
col[i] = false;
}
}
int main()
{
while (scanf("%d%d", &n, &k), ~n || ~k)
{
for (int i = 0; i < n; i ++ )
scanf("%s", g[i]);
for (int i = 0; i <= n; i ++ ) col[i] = 0;
ans = 0;
dfs(0, 0);
printf("%d\n", ans);
}
}
BFS
BFS可以在入队的时候判重 ,dijkstra可以在出队的时候判重,A*不能判重
A*算法要保证有解才可以用,不然搜索空间可能会比直接用BFS还要大呢。
模板题: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搜索: 所有BFS问题要保证两点:单调性、两段性
每次将队头取出,将可达状态全部拓展、并将产生的新的状态插入队列,等待更新
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
#define x first
#define y second
const int N = 110;
int n,m ;
int g[N][N];
int dist[N][N];
void bfs()
{
memset(dist, -1, sizeof dist);
dist[1][1] = 0;
queue<PII> q;
q.push({1, 1});
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
while (q.size())
{
PII t = q.front();
q.pop();
// printf("%d %d\n", t.x, t.y);
for (int i = 0; i < 4; i ++ )
{
int x = t.x + dx[i], y = t.y + dy[i];
if (x <= 0 || x > n || y <= 0 || y > m) continue;
if (dist[x][y] != -1) continue;
if (g[x][y]) continue;
// printf("%d %d\n", x, y);
dist[x][y] = dist[t.x][t.y] + 1;
q.push({x, y});
}
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &g[i][j]);
bfs();
printf("%d\n", dist[n][m]);
return 0;
}
广搜显威题目:
题目:
在一个 3×3 的网格中,1∼8 这 8 个数字和一个 x 恰好不重不漏地分布在这 3×3 的网格中。 例如: 1 2 3 x 4 6 7 5 8 在游戏过程中,可以把 x 与其上、下、左、右四个方向之一的数字交换(如果存在)。 我们的目的是通过交换,使得网格变为如下排列(称为正确排列): 1 2 3 4 5 6 7 8 x 例如,示例中图形就可以通过让 x 先后与右、下、右三个方向的数字交换成功得到正确排列。 交换过程如下: 1 2 3 1 2 3 1 2 3 1 2 3 x 4 6 4 x 6 4 5 6 4 5 6 7 5 8 7 5 8 7 x 8 7 8 x 现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次交换。 输入格式 输入占一行,将 3×3 的初始网格描绘出来。 例如,如果初始网格如下所示: 1 2 3 x 4 6 7 5 8 则输入为:1 2 3 x 4 6 7 5 8 输出格式 输出占一行,包含一个整数,表示最少交换次数。 如果不存在解决方案,则输出 −1。 输入样例: 2 3 4 1 5 x 7 6 8 输出样例 19
分析:
我的想法就是将x位置的数进行四个方向的交换
如果已经出现过这个状态,就continue,如果没有就加入到队列中,
所有状态也就是9!个,不多。每次查找x的位置,然后进行转换
和那个魔板【博客第二题】那题很像.
代码:(这个题以前的时候搁浅了好久,这次自己想,自己写,给写出来了,鸣炮,奏乐~~~)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <queue>
using namespace std;
char g[3][3];
unordered_map<string, int> dist;
void set(string s)
{
for (int i = 0, t = 0; i < 3; i ++ )
for (int j = 0; j < 3; j ++ )
g[i][j] = s[t ++ ];
// cout << g[0][0] << g[0][1] << g[0][2] << endl;
// cout << g[1] << endl;
// cout << g[2] << endl;
// for (int i = 0; i < 3; i ++ )
// {
// for (int j = 0; j < 3; j ++ )
// printf("%c", g[i][j]);
// puts("");
// }
}
string get()
{
string s;
for (int i = 0, t = 0; i < 3; i ++ )
for (int j = 0; j < 3; j ++ )
s += g[i][j];
// cout << s << endl;
return s;
}
string Up(string s, int x, int y)
{
set(s);
swap(g[x][y], g[x - 1][y]);
return get();
}
string Right(string s, int x, int y)
{
set(s);
swap(g[x][y], g[x][y + 1]);
return get();
}
string Down(string s, int x, int y)
{
set(s);
swap(g[x][y], g[x + 1][y]);
return get();
}
string Left(string s, int x, int y)
{
set(s);
swap(g[x][y], g[x][y - 1]);
return get();
}
int bfs(string start, string end)
{
if (start == end) return 0;
queue<string> q;
q.push(start);
dist[start] = 0;
// int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
while (q.size())
{
string t = q.front();
q.pop();
int x, y;
for (int i = 0; i < t.size(); i ++ )
if (t[i] == 'x') x = i / 3, y = i % 3;
//四个操作没问题,检查过了
string m[4];
if (x > 0) m[0] = Up(t, x, y);
if (y < 2) m[1] = Right(t, x, y);
if (x < 2) m[2] = Down(t, x, y);
if (y > 0) m[3] = Left(t, x, y);
// puts("--------------");
// for (int i = 0; i < 4; i ++ ) cout << m[i] << endl;
// puts("[------------]");
for (int i = 0; i < 4; i ++ )
if (!dist.count(m[i]))
{
dist[m[i]] = dist[t] + 1;
q.push(m[i]);
if (m[i] == end) return dist[m[i]];
}
}
return -1;
}
int main()
{
string start, end;
end = "12345678x";
for (int i = 1; i <= 9; i ++ )
{
int c; scanf("%c ", &c);
start += c;
}
// set(start);
// get();
printf("%d\n", bfs(start, end));
// cout << s << endl;
return 0;
}
浙公网安备 33010602011771号