日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[kuangbin带你飞] 专题一 简单搜索 题解(超详细注释,史上最强题解)

發布時間:2023/12/20 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [kuangbin带你飞] 专题一 简单搜索 题解(超详细注释,史上最强题解) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

[kuangbin帶你飛]是ACMer中名氣最大的題單之一,起源是上海大學的kuangbin大佬,是從基礎到進階階段的最好題單之一,雖然題量適中,題解繁多,不過大多冗雜,注釋偏少。

本專欄致力于用盡量簡短的描述,以及大量的代碼注釋,幫助同學們真正理解題目的含義與解題方法,詳細地解析代碼,從而幫助同學們提高代碼能力(當然還是要自己動手),讓同學們體驗到算法的樂趣。

本專欄會持續更新迭代,歡迎各位算法愛好者提供意見與建議,從而督促本人做出更好的題解。能夠迭代升級的,才是最強的題解。


相信大多數看這個題解的同學均是已經接觸到kuangbin專題的了,但是為了讓同學們學習路徑更平滑,我還是想稍作補充:

包含kuangbin專題在內的,ACMer學習路徑(必看):ACM 的正確入門方式是什么?

kuangbin專題合集,最全的合集:[kuangbin帶你飛]專題1-23,本專題所有題目均在上面提交

kuangbin專題合集,ACwing評測(好處是不用忍受老OJ的老舊評測機,壞處是暫時沒有更新完所有專題)


注意!kuangbin每個專題需要一定的知識基礎,所以在學習之前希望各位同學有一定的學習途徑。

以下是我常用的學習途徑:

  • (首推)ACwing算法基礎課 + 算法提高課,性價比最高的網課,包含大量基礎到進階的知識點,強烈推薦入手,既可當算法百科查閱,也可系統學習打好基礎。
  • 《算法競賽進階指南》,非常契合kuangbin專題的一本書,既有很多知識點講解,同時也提供了很多例題幫助理解,也是個很不錯的題單,牛客 和 ACwing 上都可以刷題。
  • OIWIKI,相當全的知識點介紹網站,可以通過這個網站初步了解知識點的原理與基本實現。
  • 注意!本專欄的所有題解都會寫出所有題目的主要知識點,方便同學們查找學習


    A.棋盤問題

    主要知識點:DFS,枚舉

    題意概括:

    給定一個 n×nn\times nn×n 正方形的迷宮,其中每一行和每一列只能放一個棋子,而且只有特定的 “#” 號格子,才允許放一顆棋子

    解題思路:

    類似經典的“八皇后”問題,只不過加上了只有特定位置才能放棋子這一特點

    最樸素的方式是用DFS枚舉所有 “#” 號格子,如果同一行或同一列沒有放過棋子(用一維數組記錄這一行/列是否放過棋子進行標記),才會放下棋子

    也可以像我這樣,僅枚舉每行的 ”#“ 號格子,節約了記錄行的空間。

    AC代碼:

    #include <iostream> #include <cstring> #include <algorithm>using namespace std;const int N = 10 + 10;int n, k; char g[N][N]; // 記錄迷宮 int col[N]; // 對列上是否存在棋子進行標記 int ans;void dfs(int row, int cnt) // row是當前行,cnt是已使用的棋子數 {if (cnt == k) // 已經使用k顆棋子,該分支無需繼續(剪枝){ans ++;return;}if (row == n) return; // 所有行都枚舉完了,而棋子數還未達條件for (int i = 0; i < n; i ++ ){// 枚舉從所有列上的區域// 只有當前格子可以放棋子,且當前列上沒有棋子,才能嘗試放下這顆棋子if (g[row][i] == '#' && !col[i]){col[i] = 1; // 在當前列做標記// 放下這顆棋子,并枚舉下一行dfs(row + 1, cnt + 1); col[i] = 0; // 這列不放棋子,回到原狀態}}// 這一行不放棋子,繼續枚舉下一行dfs(row + 1, cnt); }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出while(cin >> n >> k && n != -1 ){ans = 0;memset(col, 0, sizeof col);// 行數從0 ~ n - 1for (int i = 0; i < n; i ++ ) cin >> g[i];// 初始值,第0行開始,當前一共使用了0顆棋子dfs(0, 0);cout << ans << endl;}// fclose(stdout);//輸出結束return 0; }

    B.Dungeon Master(地牢大師)

    主要知識點:BFS求最短路

    題意概括:

    給定一個 L×R×CL \times R \times CL×R×C 大小的3D地牢,其中 “S” 點為起點, “E” 點為終點,每單位時間能夠向一個方格移動一格,包括上下層,無法進入 "#"號 格子。求能否到達終點,如果能到達,最小的步數是多少?

    解題思路:

    很明顯的BFS問題,從起點開始,一步一步擴散,直到遍歷到終點或者遍歷完所有可到達的點而不能到達終點。

    這道題除了代碼量稍微大一點之外,就是一道正常的BFS題

    AC代碼:

    #include <iostream> #include <cstring> #include <algorithm> #include <queue>using namespace std;const int N = 30 + 10;struct Position {int floor, x, y; };int L, R, C; char g[N][N][N]; int dist[N][N][N];int bfs(Position st, Position ed) // st為起點,ed為終點 {// 前后左右方向的向量int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};// 用隊列存儲當前坐標queue<Position> q;q.push(st);int floor = st.floor, x = st.x, y = st.y;dist[floor][x][y] = 0;while (q.size()){Position t = q.front(); q.pop();floor = t.floor, x = t.x, y = t.y;if (g[floor][x][y] == 'E') return dist[floor][x][y]; for (int i = -1; i <= 1; i += 2){if (floor + i < 0 || floor + i >= L) continue;if (dist[floor + i][x][y] != -1) continue;if (g[floor + i][x][y] == '#') continue;dist[floor + i][x][y] = dist[floor][x][y] + 1;// 由于POJ的古老評測機,這里必須有一個中間變量才能編譯Position k = {floor + i, x, y};q.push(k);}for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];if (a < 0 || a >= R || b < 0 || b >= C) continue;if (g[floor][a][b] == '#') continue;if (dist[floor][a][b] != -1) continue;dist[floor][a][b] = dist[floor][x][y] + 1;// 由于POJ的古老評測機,這里必須有一個中間變量才能編譯Position k = {floor, a, b};q.push(k);}}return -1; }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出while(cin >> L >> R >> C && L && R && C){memset(dist, -1, sizeof dist);for (int i = 0; i < L; i ++ )for (int j = 0; j < R; j ++ )cin >> g[i][j];Position st, ed;for (int i = 0; i < L; i ++ )for (int j = 0; j < R; j ++ )for (int k = 0; k < C; k ++ ){// 由于POJ的古老評測機,這里必須有一個中間變量才能編譯Position t = {i, j, k};if (g[i][j][k] == 'S') st = t;if (g[i][j][k] == 'E') ed = t;}// 用一個變量存儲函數返回值int distance = bfs(st, ed);// -1 表示到達不了終點,其他表示到達終點的時間if (distance == -1) puts("Trapped!");else printf("Escaped in %d minute(s).\n", distance);}// fclose(stdout);//輸出結束return 0; }

    C.Catch That Cow(抓住那頭牛)

    主要知識點:BFS求最短路

    題意概括:

    牛和農夫都在一個數軸上,假設牛不動,而農夫有以下三種移動方式

    (1)向正方向移動,X + 1

    (2)向負方向移動,X - 1

    (3)自身坐標翻倍,X * 2

    求農夫需要移動多少次,才能到達牛的位置,抓住牛?

    解題思路:

    一維BFS問題,模擬,秒殺一氣呵成。

    AC代碼:

    #include <iostream> #include <cstring> #include <algorithm> #include <queue>using namespace std;const int N = 1e5 + 10;int n, k; int dist[2 * N]; // 這里需要二倍長度,否則倍增的時候有可能會爆空間int bfs() {queue<int> q;q.push(n);dist[n] = 0;while (q.size()){int t = q.front(); q.pop();if (t == k) return dist[k];// t + 1 不得超過 牛的極限坐標 if (t + 1 < N && dist[t + 1] == -1) {dist[t + 1] = dist[t] + 1;q.push(t + 1);}// t - 1 不得小于 0if (t - 1 >= 0 && dist[t - 1] == -1){dist[t - 1] = dist[t] + 1;q.push(t - 1);}// t * 2 不得超過 2倍 牛的極限坐標if (t * 2 < 2 * N && dist[2 * t] == -1){dist[2 * t] = dist[t] + 1;q.push(2 * t);}}return -1; }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出cin >> n >> k;memset(dist, -1, sizeof dist);cout << bfs();// fclose(stdout);//輸出結束return 0; }

    D.Fliptile(枚舉)

    主要知識點:狀態壓縮,枚舉,遞推,模擬

    題意概括:

    給定一個 M×NM \times NM×N 的0-1矩陣,要將這個矩陣轉換成完全只有0的矩陣,每次翻轉會連帶著翻轉相鄰的四個格子(如果有的話),求最小翻轉數方案中字典序最小的。

    解題思路:

    首先排除枚舉所有節點的方案, 215×152 ^ {15 \times 15}215×15 必定超時,這是一道有點思維難度的題,我們必須具有遞推思想,要想明白:在第一行的方案確定的情況下,為了保證上一行能夠全為0,后面的所有行都可以由前一行的狀態遞推出來,也就是說,我們唯一需要枚舉的,僅僅是第一行。

    AC代碼:

    #include <iostream> #include <cstring> #include <algorithm> #include <queue>using namespace std;const int N = 15 + 10;int m, n; // st為每個方格的狀態, backup用于記錄初始狀態,方便回到初始狀態 int st[N][N], backup[N][N]; // way代表當前操作的具體矩陣,last代表最終結果的操作矩陣 int way[N][N], last[N][N]; // ans記錄最小操作個數 int ans = 0x3f3f3f3f; // 翻轉操作 void turn(int x, int y) {way[x][y] = 1; // 記錄該操作// 一系列翻轉操作if (x - 1 > 0) st[x - 1][y] = !st[x - 1][y];if (x + 1 <= m) st[x + 1][y] = !st[x + 1][y];st[x][y] = !st[x][y];if (y - 1 > 0) st[x][y - 1] = !st[x][y - 1];if (y + 1 <= n) st[x][y + 1] = !st[x][y + 1]; } // 進行第一行的操作 int count (int state) {int cnt = 0; // cnt記錄操作個數for (int i = 1; i <= n; i ++ ){if (state & (1 << i - 1)) {// 字典序從小到大的順序,i應該從右往左進行操作turn(1, n - i + 1);cnt ++;}}return cnt; }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出cin >> m >> n;// 輸入初始狀態for (int i = 1; i <= m; i ++ )for (int j = 1; j <= n; j ++ )cin >> st[i][j];// 將初始狀態做備份memcpy(backup, st, sizeof st);// 枚舉的是第一行的操作(狀態壓縮,字典序從小到大)for (int k = 0; k < 1 << n; k ++ ) {// 每次回到初始狀態memcpy(st, backup, sizeof st);memset(way, 0, sizeof way);// count函數:進行第一行的操作int t = count(k);// 由第一行的狀態,遞推之后m - 1行的狀態for (int i = 2; i <= m; i ++ ){for (int j = 1; j <= n; j ++ ){// 如果前一行的這一列的狀態為1// 那么必須在當前一行的這一列進行一次翻轉// 從而保證上一行全部為0if (st[i - 1][j]) {turn(i, j); t ++;}}}int ok = 1; // 用于標記是否成功將最后一行也變為0for (int i = 1; i <= n; i ++ )if (st[m][i]) // 只要最后一行有狀態1存在ok = 0; // 就標記為不成功if (ok)// 更新最小解,因為第一行是按字典序從小到大枚舉的// 所以操作次數相同,第一次更新的就是最小字典序解if (t < ans) {ans = t;memcpy(last, way, sizeof way);}}// 找不到可行解if (ans == 0x3f3f3f3f) puts("IMPOSSIBLE");else{// cout << ans << endl;for (int i = 1; i <= m; i ++ ){for (int j = 1; j <= n; j ++ )cout << last[i][j] << ' ';cout << endl;}}// fclose(stdout);//輸出結束return 0; }

    E.Find The Multiple(找倍數)

    主要知識點:BFS,完全二叉樹,枚舉

    題意概括:

    給定 1——2001——2001200 的整數 nnn ,求 nnn 的倍數,這個倍數需要滿足,在十進制表示中,僅由1和0兩個數字組成,這個倍數在十進制上不超過100位。

    解題思路:

    這道題最基本的思路是BFS一位數一位數地拓展,直到找到能夠整除的方案。但是 intintint 類型的數組不能存儲足夠大的數, longlonglong longlonglong 類型也是如此,只有 unsignedunsignedunsigned longlonglong longlonglong 才能勉強存下。

    我們可以用同余進行優化,只存儲每種方案的余數,這樣我們就可以使用 intintint ,從而可以擴展更多方案。

    這是一道數據很模糊的題,復雜度分析很困難,多項輸入,但不知道項數,倍數的100位也完全是干擾項,最終測出的數據,最大為 219=5242882^{19} = 524288219=524288 ,但為了求穩,還是選擇了將數組開到足夠大 。

    AC代碼:

    #include <iostream> #include <cstring> #include <algorithm> #include <queue>using namespace std;const int N = 1e8 + 10;int n; int mod[N]; // mod[i]代表的是第i個數 MOD n的余數// 第i個數是指完全二叉樹的第i個節點// 通過構建完全二叉樹來枚舉int bfs() {queue<int> q;q.push(1);while(q.size()){int t = q.front(); q.pop();if (mod[t] == 0) return t;// 拓展左孩子if (2 * t < N){mod[2 * t] = mod[t] * 10 % n;q.push(2 * t);}// 拓展右孩子if (2 * t + 1 < N){mod[2 * t + 1] = (mod[t] * 10 + 1) % n;q.push(2 * t + 1);}}return 0; }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出while (cin >> n && n){mod[1] = 1 % n;// 利用BFS,構建完全二叉樹,每一層代表一位數字,最高位為1// 當枚舉到mod == 0的情況停止int i = bfs(); // 提取mod == 0的那一個節點// 知道這個節點編號就可以求出他所有的祖先編號vector<int> ans;while(i){ans.push_back(i % 2);i /= 2; // 訪問父親節點}reverse(ans.begin(), ans.end());for (i = 0; i < ans.size(); i ++ ) cout << ans[i];cout << endl;}// fclose(stdout);//輸出結束return 0; }

    F.Prime Path(質數路徑)

    題意概括:

    給定兩個四位數的質數 aaabbbaaa 為初狀態, bbb 為末狀態,要想從初狀態變成末狀態,可以進行以下操作:

    選擇當前質數,可以通過更換某一位的數字,來將這個質數變成另一個四位質數。

    求最少需要多少次操作,才能將初狀態變成末狀態。

    解題思路:

    BFS問題,將每個四位質數作為一個狀態,并通過條件判斷能否轉移,

    AC代碼:

    #include <iostream> #include <cstring> #include <algorithm> #include <queue>using namespace std;const int N = 1e4 + 10;int a, b; bool st[N]; int dist[N]; // 距離數組,記錄需要幾步到這個狀態(質數) int primes[N], p[N]; int cnt = 0, num = 0;// 求出所有的四位質數(質數篩) void init() {for (int i = 2; i < N; i ++ ){if (!st[i]){primes[cnt ++] = i;if (i >= 1000 && i <= 9999) p[num ++] = i;}for (int j = 0; primes[j] <= N / i; j ++ ){st[primes[j] * i] = 1;if (i % primes[j] == 0) break;}} } // 判斷是否能夠轉移 bool ok(int x, int y) {int ans = 0;while (x){int t1 = x % 10;int t2 = y % 10;x /= 10, y /= 10;if (t1 != t2) ans ++;}return ans == 1; }void solve() {// 記得初始化距離數組memset(dist, -1, sizeof dist);cin >> a >> b;queue<int> q;while(q.size()) q.pop();q.push(a);dist[a] = 0;while(q.size()){int t = q.front(); q.pop();if (t == b) {cout << dist[b] << endl;return;}// 枚舉所有狀態(四位質數)for (int j = 0; j < num; j ++ ) {// 判斷能否轉移 以及 是否到達過該狀態if (ok(p[j], t) && dist[p[j]] == -1){dist[p[j]] = dist[t] + 1;q.push(p[j]);}}}if (dist[b] == -1) cout << "Impossible\n"; }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出int t;cin >> t;init();while (t --){solve();}// fclose(stdout);//輸出結束return 0; }

    G.Shuffle’m Up(洗牌)

    主要知識點:BFS求最短路

    題意概括:

    給定兩個字符串s1,s2,對其進行洗牌操作,即以下方式進行重組

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9lmtCeRJ-1653023066918)(C:\Users\86133\AppData\Roaming\Typora\typora-user-images\image-20220520083415986.png)]

    并將重組后的數組對半分,前半為s1,后半為s2,即可繼續進行洗牌操作。

    求最少需要多少次洗牌,才能將初狀態變成末狀態。

    解題思路:

    BFS問題,將s1+s2(合并)的長字符串作為狀態,用洗牌操作更新狀態,直到出現重復狀態或目標狀態

    AC代碼:

    #include <iostream> #include <cstring> #include <algorithm> #include <queue> #include <map>using namespace std;const int N = 1e5 + 10;int t; int c; string s1, s2, s; map<string, bool> st; // 記錄狀態是否出現過int bfs() {st.clear();string state = s1; string end = s;st[state] = 1;int ans = 0;while (state != end) // 洗牌,直到出現目標狀態{string cur = ""; for (int i = 0; i < c; i ++ ) {// 先加上s2的牌cur += state[i + c];// 再加上s1的牌cur += state[i];}if (st[cur]) return -1; // 出現重復狀態,不可能達到目標狀態st[cur] = 1;ans ++;state = cur;}return ans; }void solve() {cin >> c;cin >> s1 >> s2 >> s;s1 += s2; // 將s1,s2合并成為一個字符串作為狀態cout << t << ' ' << bfs() << endl; }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出int cnt;cin >> cnt;for (t = 1; t <= cnt; t ++ ){solve();}// fclose(stdout);//輸出結束return 0; }

    H.Pots(水罐)

    主要知識點:BFS求最短路

    題意概括:

    有兩個水罐,可以進行以下三類操作(六種):

    (1) 裝滿某個水罐(1號 或 2號)

    (2) 清空某個水罐(1號 或 2號)

    (3) 將某個水罐的水倒到另一個水罐(直到其中一個水罐滿 或者 空)

    狀態設置是兩個水罐中的水。

    求最少需要多少次操作,才能將初狀態變成末狀態。

    解題思路:

    BFS問題,將兩個水罐的水設為狀態,枚舉所有可能的操作,并記錄前移狀態以及其操作種類,通過末狀態進行反向遞推得到操作路徑。

    AC代碼:

    #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <map> #define x first #define y second using namespace std;typedef pair<int, int> State; // 用一對整數來代表狀態const int N = 1e5 + 10;int a, b, c;struct Ways // 操作方式 {int op, a, b; // 操作種類,操作對象State st; // 前一個狀態 }; map<State, Ways> pre; // 記錄一個狀態的前一個,反向遞推出路徑 State ed;int bfs() {State t;map<State, int> dist; // 初狀態到這個狀態所需的步數for (int i = 0; i <= a; i ++ )for (int j = 0; j <= b; j ++ ){t = {i, j};dist[t] = -1;}queue<State> q;t = {0, 0};q.push(t);dist[t] = 0;pre[t] = {0, 0, 0, t};while(q.size()){State t = q.front(); q.pop();// 達到目標狀態時,返回步數if (t.x == c || t.y == c) {ed = t; // 記錄最后狀態,方便反推return dist[t];}State next;// 操作1,將第一個水罐裝滿if (t.x < a){next = {a, t.y};if (dist[next] == -1) {q.push(next);dist[next] = dist[t] + 1;pre[next] = {1, 1, 0, t};}}// 操作2,將第二個水罐裝滿if (t.y < b){next = {t.x, b};if (dist[next] == -1) {q.push(next);dist[next] = dist[t] + 1;pre[next] = {1, 2, 0, t};}}// 操作3,將第一個水罐清空if (t.x > 0){next = {0, t.y};if (dist[next] == -1) {q.push(next);dist[next] = dist[t] + 1;pre[next] = {2, 1, 0, t};}}// 操作4,將第二個水罐清空if (t.y > 0){next = {t.x, 0};if (dist[next] == -1) {q.push(next);dist[next] = dist[t] + 1;pre[next] = {2, 2, 0, t};}}// 操作5,將第一個水罐的水倒到第二個水罐if (t.x > 0 && t.y < b){int delta = min(b - t.y, t.x);next = {t.x - delta, t.y + delta};if (dist[next] == -1) {q.push(next);dist[next] = dist[t] + 1;pre[next] = {3, 1, 2, t};}}// 操作6,將第二個水罐的水倒到第一個水罐if (t.y > 0 && t.x < a){int delta = min(a - t.x, t.y);next = {t.x + delta, t.y - delta};if (dist[next] == -1) {q.push(next);dist[next] = dist[t] + 1;pre[next] = {3, 2, 1, t};}}}return -1; }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出cin >> a >> b >> c;int n = bfs(); // n為達到目標狀態的操作數if (n == -1) {puts("impossible");return 0;}cout << n << endl;vector<Ways> v;for (int i = 0; i < n; i ++ ){v.push_back(pre[ed]); // 遞推n個操作ed = pre[ed].st; // 并得到前一個狀態}// 反向輸出,才是正確的順序,因為我們是從末狀態反向推出來的操作for (int i = n - 1; i >= 0; i -- ){Ways path = v[i];int op, x, y;op = path.op;x = path.a;y = path.b;if (op == 1){printf("FILL(%d)\n", x);}else if (op == 2){printf("DROP(%d)\n", x);}else printf("POUR(%d,%d)\n", x, y);}// fclose(stdout);//輸出結束return 0; }

    I.Fire Game(玩火)(本題提交與ACwing評測機)

    主要知識點:BFS求最短路

    題意概括:

    兩個小孩在玩火,只有草叢 “#” 才能燒起來,兩個小孩會在同時點燃兩個草叢(也可能是同一個),求把地圖上所有草叢都燒著的最短時間。

    解題思路:

    BFS問題,首先需要判斷連通塊個數,如果連通塊大于2,則兩個小孩不能通過一次點火點燃所有草叢;如果連通塊等于2,則兩個小孩需要分開點燃兩堆草叢;如果連通塊等于1,則兩個小孩可以在這一堆里面任選 1~2 個草叢點燃。

    點燃之后,計算所有草叢被點燃時的時間dist,最大的時間就是所有草叢都燒著的時間,枚舉所有方案,并記錄其中的最小值。

    AC代碼:

    #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <map> #define x first #define y second using namespace std;typedef pair<int, int> PII;const int N = 100 + 10;int n, m; char g[N][N]; int dist[N][N]; // 當前草叢燒起來的所花的最短時間(順帶記錄是否燒起來過) vector<PII> p[N]; // 記錄連通塊中的節點 int cnt; // 連通塊(草叢)的個數cntint bfs(int flag, int x1, int y1, int x2, int y2) {int mx = 0;// 判斷連通塊時千萬不能每次都更新dist(記錄是否訪問)!if (flag > 1) memset(dist, -1, sizeof dist);int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};queue<PII> q;q.push({x1, y1}); dist[x1][y1] = 0;if (flag == 1 || (x1 == x2 && y1 == y2));else {q.push({x2, y2}); // 有時需要兩個火源進行BFSdist[x2][y2] = 0;}while (q.size()){PII t = q.front(); q.pop();int x = t.x, y = t.y;if (flag == 1) {p[cnt].push_back(t);}mx = max(mx, dist[x][y]);for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];PII next = {a, b};if (a <= 0 || a > n || b <= 0 || b > m) continue;if (g[a][b] == '#' && dist[a][b] == -1){q.push(next);dist[a][b] = dist[x][y] + 1;}}}// cout << mx << endl;return mx; } // 判斷連通塊 void check() {cnt = 0;for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ )if (g[i][j] == '#' && dist[i][j] == -1) {cnt ++;if (cnt > 2) return;bfs(1, i, j, 0, 0); // 對當前連通塊進行標記} } // 枚舉兩個連通塊的情況 int both() {int mn = 1e9;// 兩個小孩必須分別在兩個連通塊中放火for (int i = 0; i < p[1].size(); i ++ )for (int j = 0; j < p[2].size(); j ++ ){PII t1 = p[1][i], t2 = p[2][j];mn = min(mn, bfs(2, t1.x, t1.y, t2.x, t2.y));}return mn; } // 枚舉一個連通塊的情況 int only() {int mn = 1e9;// 兩個小孩在同一個連通塊中放火,可以是同個點for (int i = 0; i < p[1].size(); i ++ )for (int j = 0; j < p[1].size(); j ++ ){PII t1 = p[1][i], t2 = p[1][j];mn = min(mn, bfs(3, t1.x, t1.y, t2.x, t2.y));}return mn; }int solve() {memset(dist, -1, sizeof dist);p[1].clear();p[2].clear();cin >> n >> m;for (int i = 1; i <= n; i ++) cin >> g[i] + 1;check(); // 判斷連通塊(草叢)的個數cntint ans;if (cnt > 2) ans = -1;else if (cnt == 2) ans = both();else if (cnt) ans = only();else ans = -1;return ans; }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出int t;cin >> t;for (int i = 1; i <= t; i ++ ){int ans = solve();printf("Case %d: %d\n", i, ans);}// fclose(stdout);//輸出結束return 0; }

    I.Fire!(火災)

    主要知識點:BFS求最短路

    題意概括:

    在一個迷宮里,有復數個火源 和 一位逃生者,逃生者和火源都會以1格每單位時間的速度移動,逃生者需要從迷宮邊緣逃生,求最短時間。

    解題思路:

    BFS問題,先對所有火源進行多源BFS,求出每個點被火焰燃燒的時間點,然后讓逃生者進行模擬逃生(BFS),每個點,只有在火焰燃燒到之前到達,才能更新到達時間,否則就無法到達。

    當BFS拓展到任何一個邊界時,下一步就可以直接走出迷宮。

    AC代碼:

    #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <map> #define x first #define y second using namespace std;typedef pair<int, int> PII;const int N = 1000 + 10;int n, m; char g[N][N]; // dist是火焰到達的時間點, d是逃生者到達的時間點 int dist[N][N], d[N][N]; int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1}; // 火源先拓展 void bfs1() {// 這里必須用一個較大的值來表示這個點火焰沒有到達memset(dist, 0x3f, sizeof dist);queue<PII> q1;while (q1.size()) q1.pop();// 導入所有火源for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ )if (g[i][j] == 'F'){PII t = {i, j};q1.push(t);dist[i][j] = 0;}// 正常的BFS拓展while (q1.size()) {PII t = q1.front(); q1.pop();int x = t.x, y= t.y;for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];if (g[a][b] == '#') continue;if (a <= 0 || a > n || b <= 0 || b > m) continue;if (dist[a][b] == 0x3f3f3f3f){dist[a][b] = dist[x][y] + 1;PII next = {a, b};q1.push(next);} }} } // 逃生者模擬逃生 int bfs2() {// 這里就不需要用大數標記,用-1表示是否到達過就行了。memset(d, -1, sizeof d);queue<PII> q;while (q.size()) q.pop();for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ )if (g[i][j] == 'J'){PII t = {i, j};q.push(t);d[i][j] = 0;} while (q.size()){PII t = q.front(); q.pop();int x = t.x, y = t.y;// 第一次到達邊界,答案就是加一步出迷宮if (x == 1 || x == n || y == 1 || y == m) return d[x][y] + 1;for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];if (g[a][b] == '#') continue;if (a <= 0 || a > n || b <= 0 || b > m) continue;// 只有dist大于d,才能進行拓展(這就是為什么要用大數標記dist)if (d[x][y] + 1 < dist[a][b] && d[a][b] == -1){PII next = {a, b};q.push(next);d[a][b] = d[x][y] + 1;}} } return -1; }void solve() {cin >> n >> m;for (int i = 1; i <= n; i ++ ) cin >> g[i] + 1; bfs1();int ans = bfs2();if (ans == -1) puts("IMPOSSIBLE");else cout << ans << endl;// for (int i = 1; i <= n; i ++ ) // {// for (int j = 1; j <= n; j ++ )// cout << dist[i][j] << ' ';// cout << endl;// } }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出int t;cin >> t;for (int i = 1; i <= t; i ++ ){solve();}// fclose(stdout);//輸出結束return 0; }

    J.迷宮問題

    主要知識點:BFS求最短路

    題意概括:

    給定一個 $5 \times 5 $ 的迷宮矩陣,求左上角 (0,0)(0,0)(0,0) 到 右下角 (4,4)(4,4)(4,4) 的最短路徑

    注意,輸出所有走過的點,即為路徑

    解題思路:

    BFS問題,只不過需要記錄路徑。

    在水罐那道題我們已經見識了反推路徑,這道題我們來用個新的方法,從終點開始走到起點,這樣直接得到的就是正向路徑了。

    AC代碼:

    #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <map> #define x first #define y second using namespace std;typedef pair<int, int> PII;const int N = 10 + 10;int g[N][N]; int st[N][N]; // 用一個數組記錄是否重復走過就行了 map<PII, PII> pre; // 記錄前一個節點int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};void bfs(PII ed) {memset(st, 0, sizeof st);queue<PII> q;q.push(ed);st[ed.x][ed.y] = 1;while(q.size()){PII t = q.front(); q.pop();int x = t.x, y = t.y;if (x == 0 && y == 0) return;for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];PII next;next = {a, b};if (a < 0 || b < 0 || a >= 5 || b >= 5) continue;if (g[a][b] == 1) continue;if (st[a][b] == 1) continue;q.push(next);pre[next] = t;st[a][b] = 1;}} }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出for (int i = 0; i < 5; i ++ )for (int j = 0; j < 5; j ++ )cin >> g[i][j];PII ed;ed = {4, 4};bfs(ed);PII state;state = {0, 0};while(state.x != 4 || state.y != 4){// 得到節點,直接輸出printf("(%d, %d)\n", state.x, state.y);state = pre[state];}// 最后一個節點手動輸出printf("(%d, %d)\n", state.x, state.y);// fclose(stdout);//輸出結束return 0; }

    K.Oil Deposits(石油儲量)

    主要知識點:BFS求最短路

    題意概括:

    可以認為,在以某個石油的九宮格內的石油,都是同一批石油,否則則認為不是同一批;給定一個地圖,求地圖上有多少批不同的石油。

    解題思路:

    BFS判斷連通塊問題,判斷有多少個連通塊即可,連通塊的注釋可以看下"玩火"那道題

    AC代碼:

    #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <map> #define x first #define y second using namespace std;typedef pair<int, int> PII;const int N = 100 + 10;int n, m; char g[N][N]; int st[N][N];int cnt; void bfs(int i, int j) {queue<PII> q;q.push({i, j});st[i][j] = cnt;while (q.size()){PII t = q.front(); q.pop();int x = t.x, y = t.y;for (int i = -1; i <= 1; i ++ )for (int j = -1; j <= 1; j ++ ){int a = x + i, b = y + j;if (a <= 0 || a > n || b <= 0 || b > m) continue;if (st[a][b]) continue;if (g[a][b] != '@') continue;PII next = {a, b};q.push(next);st[a][b] = 1;}} }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出while (cin >> n >> m && n){memset(st, 0, sizeof st);cnt = 0;for (int i = 1; i <= n; i ++ ) cin >> g[i] + 1;for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ ){if (g[i][j] == '@' && st[i][j] == 0) {cnt ++;bfs(i, j);}}cout << cnt << endl;}// fclose(stdout);//輸出結束return 0; }

    L.非常可樂

    主要知識點:BFS求最短路

    題意概括:

    一瓶可樂的容量為s,有兩個空杯子容量分別為n,m;

    三個杯/瓶可以相互倒,每次倒可樂算一步,求恰好平分可樂的最小步數。

    解題思路:

    類似于水罐那道題,只不過這次我們需要自己分析操作的種類。

    將三個杯/瓶分別稱為 a, b, c。

    (1)a -> b

    (2)a -> c

    (3)b -> a

    (4)b -> c

    (5)c -> a

    (6)c -> b

    倒水的方式跟水罐那道題相互倒水幾乎一樣。

    AC代碼:

    #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <map> #define x first #define y second using namespace std;typedef pair<int, int> PII; typedef pair<int, pair<int, int>> PIII; const int N = 100 + 10;int s, n, m;int dist[N][N][N]; // 記錄到某個狀態的最短步數int bfs() {memset(dist, -1, sizeof dist);queue<PIII> q;PIII start = {s, {0, 0}}; // 這里可以用結構體定義,可讀性會強點q.push(start);dist[s][0][0] = 0;while (q.size()){PIII t = q.front(); q.pop();int a = t.x, b = t.y.x, c = t.y.y;// 終止狀態// 如果其中一杯已經滿足有一半的可樂了,那么另一半一定在另外兩杯中if (a == s / 2 || c == s / 2 || b == s / 2) {int ans = dist[a][b][c];// 如果沒有一個為空(則另外一個也是一半)// 則需要再多加一步將兩個合并為另一半可樂if (a && b && c) ans ++;return ans;}if (a < s){// (1)a -> bif (b > 0){int da = min(s - a, b);PIII next = {a + da, {b - da, c}};if (dist[a + da][b - da][c] == -1) {dist[a + da][b - da][c] = dist[a][b][c] + 1;q.push(next);}}// (2)a -> cif (c > 0){int da = min(s - a, c);PIII next = {a + da, {b, c - da}};if (dist[a + da][b][c - da] == -1){dist[a + da][b][c - da] = dist[a][b][c] + 1;q.push(next);}}}if (b < n){// (3)b -> aif (a > 0){int db = min(n - b, a);PIII next = {a - db, {b + db, c}};if (dist[a - db][b + db][c] == -1){dist[a - db][b + db][c] = dist[a][b][c] + 1;q.push(next);}}// (4)b -> cif (c > 0){int db = min(n - b, c);PIII next = {a, {b + db, c - db}};if (dist[a][b + db][c - db] == -1){dist[a][b + db][c - db] = dist[a][b][c] + 1;q.push(next);}}}if (c < m){// (5)c -> aif (a > 0){int dc = min(m - c, a);PIII next = {a - dc, {b, c + dc}};if (dist[a - dc][b][c + dc] == -1) {dist[a - dc][b][c + dc] = dist[a][b][c] + 1;q.push(next);}}// (6)c -> bif (b > 0){int dc = min(m - c, b);PIII next = {a, {b - dc, c + dc}};if (dist[a][b - dc][c + dc] == -1) {dist[a][b - dc][c + dc] = dist[a][b][c] + 1;q.push(next);}}}}return -1; }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出while (cin >> s >> n >> m && s && n && m){if (s % 2){cout << "NO\n";continue;}int ans = bfs();if (ans == -1) puts("NO");else cout << ans << endl;}// fclose(stdout);//輸出結束return 0; }

    M.Find a way(找"去KFC的"路)

    主要知識點:BFS求最短路

    題意概括:

    地圖上標記了兩個人和若干個KFC的位置。

    這兩個人想去同一個KFC,求兩個人花的總時間最少為多少。

    解題思路:

    BFS問題,兩次BFS分別預處理兩個人到所有KFC的距離,最后求距離之和最小的即可

    AC代碼:

    #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <map> #define x first #define y second using namespace std;typedef pair<int, int> PII; typedef pair<int, pair<int, int>> PIII; const int N = 200 + 10;int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1}; int n, m; char g[N][N]; // dis:第一個人到地圖上點的最短距離 // d:第二個人到地圖上點的最短距離 int dis[N][N], d[N][N]; PII a, b; // 記錄兩個人的坐標 // 預處理第一個人到地圖上點的最短距離 void bfs1() {// 求最短距離的時候最好還是用大數標記,否則有的KFC走不到就麻煩了memset(dis, 0x3f, sizeof dis);queue<PII> q1;q1.push(a);dis[a.x][a.y] = 0;while (q1.size()){PII t = q1.front(); q1.pop();int x = t.x , y = t.y;for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];if (a <= 0 || a > n || b <= 0 || b > m) continue;if (g[a][b] == '#') continue;if (dis[a][b] == 0x3f3f3f3f) {dis[a][b] = dis[x][y] + 1;q1.push({a, b});}}} } // 預處理第二個人到地圖上點的最短距離 void bfs2() {memset(d, 0x3f, sizeof d);queue<PII> q;q.push(b);d[b.x][b.y] = 0;while (q.size()){PII t = q.front(); q.pop();int x = t.x , y = t.y;for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];if (a <= 0 || a > n || b <= 0 || b > m) continue;if (g[a][b] == '#') continue;if (d[a][b] == 0x3f3f3f3f) {d[a][b] = d[x][y] + 1;q.push({a, b});}}} }void solve() {vector<PII> ans; // ans 記錄KFC的位置ans.clear();for (int i = 1; i <= n; i ++ ) cin >> g[i] + 1;for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ ){if (g[i][j] == 'Y') a = {i, j};if (g[i][j] == 'M') b = {i, j};if (g[i][j] == '@') ans.push_back({i, j});}bfs1();bfs2();int mn = 1e9;// 分別求所有for (int i = 0; i < ans.size(); i ++ ){int x = ans[i].x, y = ans[i].y;mn = min(mn, dis[x][y] + d[x][y]);}cout << mn * 11 << endl; }int main() {// freopen("data.in","r",stdin); // 文件輸入// freopen("data.out","w",stdout);// 文件輸出while (cin >> n >> m){solve();}// fclose(stdout);//輸出結束return 0; }

    總結

    以上是生活随笔為你收集整理的[kuangbin带你飞] 专题一 简单搜索 题解(超详细注释,史上最强题解)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。