目录

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

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种操作:

  1. 把相邻两位数字进行交换
  2. 把某位上数字加一,然后%10
  3. 把某位数字上乘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;
}
扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄