bfs最短路与最小步数模型
bfs最短路与最小步数模型
1. 算法分析
最短路:从A点走到B点的最小距离
最小步数:从状态A到状态B的最小变化数,本质就是最短路
2. 例题
2.1 最短路
acwing1076迷宫问题
给定N*N数组,每个元素只有0和1,求从(0, 0)走到(n - 1, n - 1)的最短路,输出其路径
#include <bits/stdc++.h>
using namespace std;
int const N = 1e3 + 10;
int a[N][N], st[N][N], n;
int pre[N * N];
queue<pair<int, int> >q;
vector<pair<int, int> > path;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
void bfs(pair<int, int> start) {
q.push(start);
st[start.first][start.second] = 1;
while (q.size()) {
auto t = q.front();
q.pop();
for (int i = 0; i < 4; ++i) {
int x = t.first + dx[i], y = t.second + dy[i];
if (x < 0 || x > n - 1 || y < 0 || y > n - 1) continue;
if (st[x][y] || a[x][y] == 1) continue;
pre[x * n + y] = t.first * n + t.second;
if (x == n - 1 && y == n - 1) return;
st[x][y] = 1;
q.push({x, y});
}
}
}
pair<int, int> get_pos(int x) {
pair<int, int> res;
res.first = x / n;
res.second = x % n;
return res;
}
int main() {
cin >> n;
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
cin >> a[i][j];
bfs({0, 0});
int cur = (n - 1) * n + n - 1;
while (1) {
path.push_back(get_pos(cur));
cur = pre[cur];
if (cur == 0) break;
}
path.push_back({0, 0});
reverse(path.begin(), path.end());
for (auto p: path)
cout << p.first << " " << p.second << endl;
return 0;
}
acwing188武士风度的牛
给定一张N*M的地图,起始地点为K,终点为H,求从K到H的最少步数
#include <bits/stdc++.h>
using namespace std;
int const N = 160;
typedef pair<int, int> PII;
typedef pair<PII, int> PIII;
int n, m;
char mp[N][N];
int st[N][N];
PII S;
int dx[] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[] = {1, 2, 2, 1, -1, -2, -2, -1};
int bfs(){
queue<PIII> q;
q.push({S, 0});
st[S.first][S.second] = 1;
while (q.size()) {
auto t = q.front();
q.pop();
auto ver = t.first;
int step = t.second;
for (int i = 0; i < 8; ++i) {
int x = ver.first + dx[i];
int y = ver.second + dy[i];
if (x < 1 || x > n || y < 1 || y > m) continue;
if (mp[x][y] == 'H') return step + 1;
if (st[x][y] || mp[x][y] != '.') continue;
st[x][y] = 1;
q.push({{x, y}, step + 1});
}
}
}
int main () {
cin >> m >> n;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> mp[i][j];
if (mp[i][j] == 'K') S = {i, j};
}
}
cout << bfs();
return 0;
}
2.2 最小步数
2.2.1 基础最小步数模型
acwing1107魔板
初始状态为1234 5678(第一行为1234,第二行为5678),每次允许三种操作:
A:交换上下两行;
B:将最右边的一列插入到最左边;
C:魔板中央对的4个数作顺时针旋转。
输入末状态,求从初状态到末状态的最小步数,并打印路径
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <queue>
using namespace std;
char g[2][4];
unordered_map<string, pair<char, string>> pre;
unordered_map<string, int> dist;
void set(string state)
{
for (int i = 0; i < 4; i ++ ) g[0][i] = state[i];
for (int i = 7, j = 0; j < 4; i --, j ++ ) g[1][j] = state[i];
}
string get()
{
string res;
for (int i = 0; i < 4; i ++ ) res += g[0][i];
for (int i = 3; i >= 0; i -- ) res += g[1][i];
return res;
}
string move0(string state)
{
set(state);
for (int i = 0; i < 4; i ++ ) swap(g[0][i], g[1][i]);
return get();
}
string move1(string state)
{
set(state);
int v0 = g[0][3], v1 = g[1][3];
for (int i = 3; i >= 0; i -- )
{
g[0][i] = g[0][i - 1];
g[1][i] = g[1][i - 1];
}
g[0][0] = v0, g[1][0] = v1;
return get();
}
string move2(string state)
{
set(state);
int v = g[0][1];
g[0][1] = g[1][1];
g[1][1] = g[1][2];
g[1][2] = g[0][2];
g[0][2] = v;
return get();
}
int bfs(string start, string end)
{
if (start == end) return 0;
queue<string> q;
q.push(start);
dist[start] = 0;
while (!q.empty())
{
auto t = q.front();
q.pop();
string m[3];
m[0] = move0(t);
m[1] = move1(t);
m[2] = move2(t);
for (int i = 0; i < 3; i ++ )
if (!dist.count(m[i]))
{
dist[m[i]] = dist[t] + 1;
pre[m[i]] = {'A' + i, t};
q.push(m[i]);
if (m[i] == end) return dist[end];
}
}
return -1;
}
int main()
{
int x;
string start, end;
for (int i = 0; i < 8; i ++ )
{
cin >> x;
end += char(x + '0');
}
for (int i = 1; i <= 8; i ++ ) start += char('0' + i);
int step = bfs(start, end);
cout << step << endl;
string res;
while (end != start)
{
res += pre[end].first;
end = pre[end].second;
}
reverse(res.begin(), res.end());
if (step > 0) cout << res << endl;
return 0;
}
2.2.3 有条件的最小步数模型
ICPC2016北京 E - What a Ridiculous Election
初始字符串为12345,有3种操作:
- 把相邻两位数字进行交换
- 把某位上数字加一,然后%10
- 把某位数字上乘2,然后%10
操作2最多进行3次,操作3最多进行2次
给定1e5个末状态字符串,求从12345到达末状态的最小步数,如果不能到达末状态,输出-1
/* 计算末状态可以知道,可达的末状态数目规模为1e6,因此可以先预处理出所有的末状态
处理的方法就是最小步数+条件限制,把条件限制当成数组计算 */
#include <bits/stdc++.h>
using namespace std;
int const N = 1e7 + 10;
unordered_map<string, int> res;
unordered_map<string, int> mp;
unordered_map<int, string> i2s;
struct point {
int status;
int k1, k2;
int step;
};
int st[N][4][3]; // 第二维为限制条件1,第三维为限制条件2
int num;
int mapping(string s) {
if (!mp.count(s)) mp[s] = ++num;
i2s[mp[s]] = s;
return mp[s];
}
queue<point> q;
void bfs() {
// 初始状态
q.push({mapping("12345"), 3, 2, 0});
st[mapping("12345")][3][2] = 1;
res["12345"] = 0;
while (q.size()) {
auto t = q.front();
q.pop();
string ori_str = i2s[t.status];
int k1_left = t.k1, k2_left = t.k2, last_step = t.step;
// 第一种变化
for (int i = 0; i <= 3; ++i) {
swap(ori_str[i], ori_str[i + 1]);
if (!st[mapping(ori_str)][k1_left][k2_left]) {
st[mapping(ori_str)][k1_left][k2_left] = 1;
if (!res.count(ori_str)) res[ori_str] = last_step + 1;
else res[ori_str] = min(res[ori_str], last_step + 1);
q.push({mapping(ori_str), k1_left, k2_left, last_step + 1});
}
swap(ori_str[i], ori_str[i + 1]);
}
// 第二种变化
if (k1_left) { // 限制条件1还能进行
for (int i = 0; i < 5; ++i) {
string new_str = ori_str;
new_str[i] = ((new_str[i] - '0' + 1) % 10) + '0';
if (!st[mapping(new_str)][k1_left - 1][k2_left]) {
st[mapping(new_str)][k1_left - 1][k2_left] = 1;
if (!res.count(new_str)) res[new_str] = last_step + 1;
else res[new_str] = min(res[new_str], last_step + 1);
q.push({mapping(new_str), k1_left - 1, k2_left, last_step + 1});
}
}
}
// 第三种变化
if (k2_left) { // 限制条件2还能进行
for (int i = 0; i < 5; ++i) {
string new_str = ori_str;
new_str[i] = ((new_str[i] - '0') * 2) % 10 + '0';
if (!st[mapping(new_str)][k1_left][k2_left - 1]) {
st[mapping(new_str)][k1_left][k2_left - 1] = 1;
if (!res.count(new_str)) res[new_str] = last_step + 1;
else res[new_str] = min(res[new_str], last_step + 1);
q.push({mapping(new_str), k1_left, k2_left - 1, last_step + 1});
}
}
}
}
}
int main() {
ios::sync_with_stdio(false);
bfs();
string s;
while (cin >> s) {
if (res.count(s)) {
cout << res[s] << endl;
}
else cout << -1 << endl;
}
return 0;
}
2.2.3 卡内存+有条件的最小步数模式
ICPC North Central NA Contest 2018 G. Tima goes to Xentopia
题意:有一张无向图N个点M条边,每条边有颜色,可能为白,红,蓝,同时每条边还有一个边权。小A从1号点出发走到n号点,问是否存在一条经过k1条红边,k2条蓝边的最短路,如果有,输出其最短路权值;如果没有,输出-1
N ~ 450, M ~ 1100, K1 * K2 <= 800
题解:
本题求解一个带有条件的最短路,是一个模板题。但是如果开始dis[450][800][800],那么mle,因此由k1*k2<=800,则dis[450][800][100],首先要判断k1和k2的大小,小的当成k2.有条件的最短路和没条件的最短路区别在于放入st数组和dis数组设置。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL, int> PLI;
LL const INF = 0x3f3f3f3f3f3f3f3f;
struct point {
int id, r, b;
LL dis;
point(int _id, int _r, int _b, LL _dis) : id(_id), r(_r), b(_b), dis(_dis) {}
bool operator < (const point & t) const {
return dis > t.dis;
}
};
LL dis[500][810][100];
int st[500][810][100];
int k1, k2, n, m, s, t, idx, e[3000], ne[3000], h[500], w[3000], color[3000];
void add(int a, int b, int c, int col) {
e[idx] = b, w[idx] = c, color[idx] = col, ne[idx] = h[a], h[a] = idx++;
}
void Dijkstra() {
memset(dis, 0x3f, sizeof dis);
priority_queue<point> q;
dis[s][0][0] = 0;
q.push({s, 0, 0, 0});
while(!q.empty()) {
point temp = q.top();
q.pop();
if(st[temp.id][temp.r][temp.b]) continue; // 记录的时候要记点,边1,边2
st[temp.id][temp.r][temp.b] = 1;
for(int i = h[temp.id]; ~i; i = ne[i]) {
point t = {e[i], temp.r + (color[i] == 1), temp.b + (color[i] == 2), temp.dis + w[i]};
if(t.r > k1 || t.b > k2) continue;
if(t.dis < dis[t.id][t.r][t.b]) {
dis[t.id][t.r][t.b] = t.dis;
q.push(t);
}
}
}
printf("%lld\n", dis[t][k1][k2] == INF ? -1 : dis[t][k1][k2]);
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> m >> k1 >> k2;
bool change = false;
if (k1 < k2) { // 保证第二维放入大的
swap(k1, k2);
change = true;
}
for (int i = 1, a, b, weight, colorr; i <= m; ++i) {
cin >> a >> b >> weight >> colorr;
if (change) colorr = 3 - colorr;
add(a, b, weight, colorr);
add(b, a, weight, colorr);
}
cin >> s >> t;
Dijkstra();
return 0;
}