zoukankan      html  css  js  c++  java
  • 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种操作:

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

    2.2.4 思维最小步数

    acwing1208. 翻硬币
    题意: 一开始给定两个字符串a和b,字符串由'*'和'o'组成。每次变化可以将相邻的两个字符同时变化(*->0, 0->*),问最少多少次变化可以将a变成b?字符串a、b的长度均小于等于100.
    题解: 题目咋一看是最小步数模型,但是n能到达100,bfs最小步数必然超时。考虑每次变化的关系:每次变化将相邻的两个字符反转,相当于异或操作。对于异或操作来说,异或奇数次可以规约到1次,异或偶数次可以规约到0次,同时异或没有先后顺序之分。因此,对于所有的变化来说,相当于有n个开关,每个开关控制a[i]和a[i+1]。每个开关只有按和不按两种情况,要求最小的步数,只需要奇数次都规约到1次,偶数次都规约到0次,然后按下的开关是必须的即可。因此,从前往后扫描,如果a[i]!=b[i],那么需要按下开关i,然后把a[i]和a[i+1]反转,这样每次按下都是必须的,也就是最少的步数。
    代码:

    #include <bits/stdc++.h>
    
    using namespace std;
    
    string s, e;
    
    void turn(int x) {
        if (s[x] == '*') s[x] = 'o';
        else s[x] = '*';
    }
    
    int main() {
        cin >> s >> e;
        int res = 0;
        for (int i = 0; i < s.size() - 1; ++i) {
            if (s[i] != e[i]) res++, turn(i), turn(i + 1);
        }
        cout << res << endl;
        return 0;
    }
    
  • 相关阅读:
    [leetcode] Copy List with Random Pointer
    [leetcode] Single Number II
    团队项目NABCD模型的需求分析
    团队项目的分工及绩效评估方法
    软件工程结对作业实验报告
    Java jdbc链接 mySQL 写的crud
    从高版本JDK换成低版本JDK报错Unsupported major.minor version 52.0的解决方案
    红黑树简介
    再学HTML之一
    Java script 的dom编程
  • 原文地址:https://www.cnblogs.com/spciay/p/13383121.html
Copyright © 2011-2022 走看看