AcWing.算法提高课-第二章 搜索

洪水覆盖算法(Flood Fill)

算法介绍

洪水填充(Flood fill)算法:从一个起始节点开始把附近与其连通的节点提取出或填充成不同颜色颜色,直到封闭区域内的所有节点都被处理过为止,是从一个区域中提取若干个连通的点与其他相邻区域区分开(或分别染成不同颜色)的经典算法。

相关参数

洪水填充算法接受三个参数:

  •     起始节点,目标节点特征和针对提取对象要执行的处理。

实现方式

  • 目前有许多实现方式,基本上都显式的或隐式的使用了队列或者栈。
  • 洪水填充算法实现最常见有四邻域填充法(不考虑对角线方向的节点),八邻域填充法(考虑对角线方向的节点),基于扫描线填充方法。

根据实现又可以分为递归与非递归(基于栈)。

  •     最简单的实现方法是采用深度优先搜索的递归方法,也可以采用广度优先搜索的迭代来实现。基于递归实现的泛洪填充算法有个致命的缺点,就是对于大的区域填充时可能导致栈溢出错误,基于扫描线的算法实现了一种非递归的洪水填充算法。
  •     除提出连通区域外,还可以应用于计算从某一节点开始,到可能到达其他所有节点的距离。比如解决像走迷宫这类的问题。

例题讲解

池塘计数

思路分析:模板题。这道题遍历所有的点如果是W就进行bfs找到与他相邻的W并把它标记成".",每次进行一次bfs即是一个连通块。

#include<bits/stdc++.h>
using namespace std;

typedef pair<int, int> PII;

const int N = 1e3+10;
char g[N][N];
int n,m,ans = 0;
//八连通
int dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};
int dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};



void bfs(int x,int y){
    g[x][y] = '.';
    queue<PII> q;
    q.push({x,y});
    while(!q.empty()){
        auto t = q.front(); q.pop();
        for(int i = 0; i < 8; i++){
            int sx = t.first+dx[i],sy = t.second+dy[i];
            if(sx >= 0 && sy >= 0 && sx < n && sy < m && g[sx][sy] == 'W'){
                g[sx][sy] = '.'; //把找到的点标记为点,避免再次查找
                q.push({sx,sy});
            }
        }
    }
}

int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i++) cin >> g[i];
    for(int i = 0; i < n; i++){
        for(int j = 0; j < m; j++){
            if(g[i][j] == 'W'){
                ans++;
                bfs(i,j);
            }
        }
    }
    cout << ans << endl;
    return 0;
}

城堡问题

二进制取位就可以知道是哪个墙挡住,比如0110,是有东墙和北墙。

  • - 1 第零位,对应二进制数 0001 , 表示有西墙
  • - 1 第一位,对应二进制数 0010 , 表示有北墙
  • - 1 第二位,对应二进制数 0100 , 表示有东墙
  • - 1 第三位,对应二进制数 1000 , 表示有南墙

#include<bits/stdc++.h>
using namespace std;

typedef pair<int, int> PII;

const int N = 1e2+10;
int n,m;
int g[N][N];
bool st[N][N];
int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};

int bfs(int sx,int sy){
    st[sx][sy] = 1;
    queue<PII> q;
    q.push({sx,sy});
    int area = 0;
    while(!q.empty()){
        auto t = q.front(); q.pop();
        area++;
        for(int i = 0; i < 4; i++){
            int x = t.first+dx[i],y = t.second+dy[i];
            if(x >= 0 && x < n && y >= 0 && y < m && !st[x][y] && !(g[t.first][t.second] >> i & 1)){ //如果是0就表示无墙
                st[x][y] = 1;
                q.push({x,y});
            }
        }
    }
    return area;
}

int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i++){
        for(int j = 0; j < m; j++) cin >> g[i][j];
    }
    int cnt = 0,area = 0;
    for(int i = 0; i < n; i++){
        for(int j = 0; j < m; j++){
            if(!st[i][j]){
                area = max(area,bfs(i,j)); //统计最大值
                cnt++;
            }
        }
    }
    cout << cnt << endl << area << endl;
    return 0;
}

山峰和山谷

思路分析:有时候从模板题上多统计一下额外信息,这道题就是统计两个点(块)之间的关系。从题意中我们可以知道如果这个块比周围的点要低,就是山谷;如果这个块比其他点要高,就是山峰;如果一样高即使山谷又是山峰。那么这道题思路就是这样。

#include <bits/stdc++.h>
using namespace std;

const int N = 1e3 + 10;
int dx[] = { -1, -1, 0, 1, 1, 1, 0, -1 };
int dy[] = { 0, 1, 1, 1, 0, -1, -1, -1 };
int w[N][N];
bool vis[N][N];
int n, ans1, ans2;

void bfs(int sx, int sy) {
    queue<pair<int, int> > q;
    q.push({ sx, sy });
    int val = w[sx][sy];
    vis[sx][sy] = 1;
    bool flag1 = 0, flag2 = 0;
    while (!q.empty()) {
        auto t = q.front();
        q.pop();
        int x = t.first, y = t.second;
        for (int i = 0; i < 8; i++) {
            int nx = x + dx[i];
            int ny = y + dy[i];
            if (nx >= 1 && nx <= n && ny >= 1 && ny <= n) {
                if (w[nx][ny] != val) {
                    if (w[nx][ny] > val)
                        flag2 = 1;
                    if (w[nx][ny] < val)
                        flag1 = 1;
                } else if (!vis[nx][ny]) {
                    vis[nx][ny] = 1;
                    q.push({ nx, ny });
                }
            }
        }
    }
    //注意:这里不能只判断一个值,因为如果周围的山参差不齐那他什么都不是
    if (!flag1 && !flag2) //如果一样高
        ans1++, ans2++;
    if (flag1 && !flag2) //高于其他山
        ans1++;
    if (!flag1 && flag2) //低于其他山
        ans2++;
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) cin >> w[i][j];
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (!vis[i][j])
                bfs(i, j);
        }
    }
    cout << ans1 << " " << ans2 << endl;
    return 0;
}

最短路模型

算法介绍

        BFS可以求边权相等的最短路,也被称为最短路模型。这种题往往用图论建图比较麻烦,但是BFS会实现简单一点。

例题讲解

迷宫问题

思路分析:这道题就是算法基础课里的走迷宫,只不过这次他让输出最短路径。这道题也算是模板题去统计一下额外信息。记录路径方式也简单,就是记录当前他的状态从哪个状态转换来的,换句话说就是他从哪个地方走过来的,很像链表的存储。

#include<bits/stdc++.h>
using namespace std;

typedef pair<int, int> PII;

int n;
const int N = 1e3+10;
int g[N][N],st[N][N];
PII pre[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

void bfs(int x,int y){
    st[x][y] = 1;
    queue<PII> q;
    q.push({x,y});
    while(!q.empty()){
        auto t = q.front(); q.pop();
        for(int i = 0; i < 4; i++){
            int sx = t.first+dx[i],sy = t.second+dy[i];
            if(sx >= 0 && sy >= 0 && sx < n && sy < n && st[sx][sy] == 0 && g[sx][sy] == 0){
                q.push({sx,sy});
                pre[sx][sy] = t; //记录当前状态是从哪个状态来的
                st[sx][sy] = 1;
            }
        }
    }
}

int main()
{
    cin >> n;
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++) cin >> g[i][j];
    }
    bfs(n-1,n-1); //应为路径是从后回溯到前的,所以倒置找最短路,刚好是正序输出
    PII end = {0,0};
    while(true){
		cout  << end.first << " " << end.second << endl; 
		if(end.first == n-1 && end.second == n-1) break;
		end = pre[end.first][end.second];
	}
    return 0;
}

武士风度的牛

思路分析:模板题。算法基础课-迷宫问题,就是把方向数组变换一下,四连通改成日字八连通,其他地方不用改。

#include<bits/stdc++.h>
using namespace std;

typedef pair<int, int> PII;

const int N = 1e3+10;
int n,m;
char g[N][N];
int d[N][N];
bool st[N][N];
//日字八连通
int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};

int bfs(int x,int y,int ex,int ey){
    memset(d, -1, sizeof d);
    d[x][y] =  0;
    queue<PII> q;
    q.push({x,y});
    while(!q.empty()){
        auto t = q.front(); q.pop();
        for(int i = 0; i < 8; i++){
            int sx = t.first+dx[i],sy = t.second+dy[i];
            if(sx >= 0 && sy >= 0 && sx < n && sy < m && g[sx][sy] == '.' && d[sx][sy] == -1){
                d[sx][sy] = d[t.first][t.second]+1;
                q.push({sx,sy});
            }
        }
    }
    return d[ex][ey];
}

int main()
{
    cin >> m >> n;
    int sx,sy,ex,ey;
    for(int i = 0; i < n; i++){
        for(int j = 0; j < m; j++){
            char c; cin >> c;
            if(c == 'K'){
                sx = i,sy = j;
                g[i][j] = '.';
            }
            else if(c == 'H'){
                ex = i,ey = j;
                g[i][j] = '.';
            }
            else g[i][j] = c;
        }
    }
    cout << bfs(sx,sy,ex,ey) << endl;
    
    return 0;
}

抓住那头牛

思路分析:跟模板题很想就是把八连通或四连通的方向状态转移换成他指定的x+1,x-1,x*2,就行了,这里给出两种写法,STL实现队列,和数组模拟队列

注意 这里需要判断是否越界,一定要把判断越界条件放在前面,不然会数组越界!!!

//STL:
#include <bits/stdc++.h>  
using namespace std;  
  
const int N = 1e5 + 10;  
int d[N];  
int n, k;  
  
int bfs() {  
    memset(d, -1, sizeof d);  
    d[n] = 0;  
    queue<int> q;  
    q.push(n);  
  
    while (!q.empty()) {  
        int t = q.front();  
        q.pop();  
        if (t == k) return d[k];  
        if (t * 2 < N && d[t * 2] == -1) {  
            d[t * 2] = d[t] + 1;  
            q.push(t * 2);  
        }  
        if (t - 1 >= 0 && d[t - 1] == -1) {  
            d[t - 1] = d[t] + 1;  
            q.push(t - 1);  
        }  
        if (t + 1 < N && d[t + 1] == -1) {  
            d[t + 1] = d[t] + 1;  
            q.push(t + 1);  
        }  
    }  
    return -1;  
}  
  
int main() {  
    cin >> n >> k;  
    cout << bfs() << endl;  
    return 0;  
}
//数组模拟
//因为不知名原因发现这里把范围判断放在后面也可以,但是最好还是放在前面,STL一定是前面
#include<bits/stdc++.h>
using namespace std;

const int N = 1e5+10;
int d[N];
int q[N];
int n,k; 

int bfs(){
    memset(d, -1, sizeof d);
    d[n] = 0;
   	q[0] = n;
   	int hh = 0,tt = 0;
    while(hh <= tt){
        auto t = q[hh++]; 
        if(t == k) return d[k];
        if(d[t*2] == -1 && t*2 < N){
            d[t*2] = d[t]+1;
            q[++tt] = t*2;
        }
        if(d[t-1] == -1 && t-1 >= 0){
            d[t-1] = d[t]+1;
            q[++tt] = t-1;
        }
        if(d[t+1] == -1 && t+1 < N){
            d[t+1] = d[t]+1;
            q[++tt] = t+1;
        }
    }
    return -1;
}

int main()
{
    cin >> n >> k;
    cout << bfs() << endl;
    return 0;
}

多源BFS

算法介绍

        多源 BFS 是指从多个源点同时进行广度优先搜索的算法。在传统的 BFS 中,我们通常从一个起始点开始,逐层遍历所有的相邻节点。而在多源 BFS 中,我们可以同时从多个源点开始,从这些源点出发,逐层向外扩展,直到达到目标或者遍历完整个图。

多源 BFS 可以用于解决一些问题,例如:

  •     多个人同时逃生: 在一个迷宫中,有多个人同时被困在不同的位置,需要找到最短路径逃离迷宫。可以从这些人的位置同时开始 BFS,第一个相遇的点就是大家逃生的最短路径。
  •     多点到达目标问题: 在一些网络传播或者路由问题中,多个点需要同时到达某个目标点,可以使用多源 BFS 找到最短路径。
  •     并行计算: 在一些并行计算的场景中,多源 BFS 可以用于并行地计算多个点到其他点的最短路径。

 多源 BFS 的实现方式与单源 BFS 类似,只是需要同时从多个源点开始扩展。通常使用队列来进行层次遍历,每一层表示到达目标的步数。

例题讲解

矩阵距离

思路分析:这道题多源BFS的模板题。算法实现就是把所以为1的点都入队,因为最近1-0的距离,也是最近0-1的距离,正常进行BFS就行了

#include<bits/stdc++.h>
using namespace std;

typedef pair<int, int> PII;

const int N = 1e3+10;
char g[N][N];
int d[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int n,m;

void bfs(){
    memset(d,-1,sizeof d);
    queue<PII> q;
    for(int i = 0; i < n; i++){
        for(int j = 0; j < m; j++){
            if(g[i][j] == '1'){ //所有为1的点都入队
                d[i][j] = 0;
                q.push({i,j});
            }
        }
    }
    while(!q.empty()){
        auto t = q.front(); q.pop();
        for(int i = 0; i < 4; i++){
            int sx = t.first+dx[i],sy = t.second+dy[i];
            if(sx >= 0 && sy >= 0 && sx < n && sy < m && d[sx][sy] == -1){
                d[sx][sy] = d[t.first][t.second]+1;
                q.push({sx,sy});
            }
        }
    }
}

int main(){
    cin >> n >> m;
    for(int i = 0; i < n; i++) cin >> g[i];
    bfs();
    for(int i = 0; i < n; i++){
        for(int j = 0; j < m; j++){
            cout << d[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

 最小步数模型

算法介绍

        求题意给定的一种状态,到另一种状态的最短距离,最小变换次数。

例题讲解

魔板

思路分析:这道题是对BFS的应用和八数码那道题很像,都是需要将二维先映射成一维,再对他进行如上A,B,C三种操作,一步一步扩展变换,即可找到指定状态,要保证方案字典序最小,那每次按照A,B,C的顺序去扩展,第一次找到状态与指定状态相同时,那字典序一定最小。

注意:这里的变换代码不好实现,但是发现只有8个数,那么手动暴力也可以。

A = {s[7], s[6], s[5], s[4], s[3], s[2], s[1], s[0]};
B = {s[3], s[0], s[1], s[2], s[5], s[6], s[7], s[4]};

C = {s[0], s[6], s[1], s[3], s[4], s[2], s[5], s[7]};

#include<bits/stdc++.h>
using namespace std;

typedef pair<string, string> PII;
map<string,bool> mp;
queue<PII> q;

int main()
{
    string s;
    for(int i = 1; i <= 8; i++){
        char c; cin >> c;
        s += c;
    }
    q.push({"12345678",""});
    while(!q.empty()){
        auto t = q.front(); q.pop();
        int res = t.first,now = t.second;
        //找到答案
        if(s == res){
            if(!now.size()) cout << 0 << endl;
            else{
                cout << now.size() << endl << now << endl;
                break;
            }
        }
        string A, B, C;
        //状态变换
        A = {s[7], s[6], s[5], s[4], s[3], s[2], s[1], s[0]};
        B = {s[3], s[0], s[1], s[2], s[5], s[6], s[7], s[4]};
        C = {s[0], s[6], s[1], s[3], s[4], s[2], s[5], s[7]};
        //标记访问,now代表操作方案,s代表变换后的样子
        if(!mp[A]) mp[A] = 1, q.push({A,now+'A'});
        if(!mp[B]) mp[B] = 1, q.push({B,now+'B'});
        if(!mp[C]) mp[C] = 1, q.push({C,now+'C'});
    }
    return 0;
}

双端队列广搜

算法介绍

        在最基本的广度优先搜索中,每次沿着分支的扩展都记为“一步”,我们通过逐层搜索,解决了从起始状态到每个状态的最小步数的问题。这其实等价于在一张边权均为1的图上执行广度优先遍历,求出每个点相对于起点的最短距离(层次)。

由于广度优先遍历具有“两段性”和“单调性”,从而我们可以得知,每个状态在第一次被访问并且入队时,计算出的步数即为所求的最短步数。

当出现边权不是0就是1的时候,可以考虑采用双端队列BFS的方法来进行求解。

基本思路:

  •     如果拓展出来的点的边权是0的话,就添加到队头
  •     如果拓展出来的点的边权是1的话,就添加到队尾

正确性:

在通过上述的方式添加元素后,队列仍然能够满足“两段性”和“单调性”,所以所求的结果即为最短路(层次)。

例题讲解

电路维修

思路分析:

        首先我们可以这样想问题,把每条边都建出来如果当前边有那么边权是0,不然边权为1,也就是需要反转一次,跑最短路即可。

        BFS的想法也类似,用双端队列即可实现。但是这里比较难得是“建图”,需要知道的是,你要到达的那个点的坐标不一定是你当前有字符格子的坐标,所以说我们也要关注他是从哪个格子走过去的。如下图:

#include<bits/stdc++.h>
using namespace std;

typedef pair<int, int> PII;

int n,m;
const int N = 1e3+10;
char g[N][N];
int dist[N][N];
bool st[N][N];
//构建方向数组,cs为当前理想状态下如果要达到那个点需要的形状。
//dx,dy为要到达顶点的方式,ix,iy是从哪个格子走
char cs[6] = "\\/\\/";
int dx[4] = {-1, -1, 1, 1}, dy[4] = {-1, 1, 1, -1};
int ix[4] = {-1,-1,0,0},iy[4] = {-1,0,0,-1};


int bfs(){
    deque<PII> q;
    q.push_back({0,0});
    memset(st, 0, sizeof st);
    memset(dist,0x3f,sizeof dist);
    dist[0][0] = 0;
    while(!q.empty()){
        auto t = q.front(); q.pop_front();
        int x = t.first,y = t.second;
        if(st[x][y]) continue;
        st[x][y] = 1;
        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){
                int ga = x+ix[i],gb = y+iy[i];
                int w = (g[ga][gb] != cs[i]);
                int d = dist[x][y] + w;
                if(d <= dist[a][b]){
                    dist[a][b] = d;
                    if(!w) q.push_front({a,b}); //是0就入队头,否则1入队尾
                    else q.push_back({a,b});
                }
            }
        }
    }
    return dist[n][m];
}

int main()
{
	cin >> n >> m;
	for(int i = 0; i < n; i++) scanf("%s",g[i]);
	int t = bfs();
	if(t == 0x3f3f3f3f) cout << "NO SOLUTION" << endl;
	else{
		cout << bfs() << endl;
	}
    return 0;
}

双向广搜

算法介绍

为什么发明双向广搜??

        我们发现在有些题中单向的广搜有些太慢了,所以我们考虑可不可以从起点和终点同时开始搜索?因为是从起点和终点同时搜索的,那么粗略估计一下可以优化一半的时间。

搜索过程

        我们同时从起点和终点开始扩展,那么当我的起点和终点搜到了一个共同的点,也就是相交了,那么就表示一定可以搜索到,否则搜不到。

               

结点扩展顺序

          双向扩展结点,在两个方向的扩展顺序上,可以轮流交替进行,但由于大部分的解答树并不是棵完全树,在扩展完一层后,下一层则选择结点个数较少的那个方向先扩展,可以克服两个方向结点生成速度不平衡的状态,明显提高搜索效率。

例题讲解

字串变换


思路分析:

我们可以存储两个状态队列,一个是起始状态另一个是目标状态,每次扩展较少的一边,如果发现一个共同状态,那么表示搜索成功,判断是否在十步以内返回,否则继续搜索。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <unordered_map>

using namespace std;

const int N = 6;

int n;
string A, B;
string a[N], b[N];

int extend(queue<string>& q, unordered_map<string, int>&da, unordered_map<string, int>& db, string a[N], string b[N])
{
    int d = da[q.front()];
    while (q.size() && da[q.front()] == d) //要扩展这一层,而不是一个点,所有距离为d的点都要进行扩展
    {
        auto t = q.front();
        q.pop();

        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < t.size(); j ++ )
                if (t.substr(j, a[i].size()) == a[i])
                {
                    string r = t.substr(0, j) + b[i] + t.substr(j + a[i].size()); //substr返回从i到i+j中间的字串
                    if (db.count(r)) return da[t] + db[r] + 1;
                    if (da.count(r)) continue;
                    da[r] = da[t] + 1;
                    q.push(r);
                }
    }

    return 11;
}

int bfs()
{
    if (A == B) return 0;
    queue<string> qa, qb;
    unordered_map<string, int> da, db;

    qa.push(A), qb.push(B);
    da[A] = db[B] = 0;

    int step = 0;
    while (qa.size() && qb.size())
    {
        int t;
        //哪边少扩展哪边保持平衡更容易搜到答案,而且注意参数要正反去填,因为是从后向前和从前向后
        if (qa.size() < qb.size()) t = extend(qa, da, db, a, b);
        else t = extend(qb, db, da, b, a);

        if (t <= 10) return t;
        if ( ++ step == 10) return -1; //如果超过十层就不可能在十步以内搜到答案,算是一个剪枝。
    }

    return -1;
}

int main()
{
    cin >> A >> B;
    while (cin >> a[n] >> b[n]) n ++ ;

    int t = bfs();
    if (t == -1) puts("NO ANSWER!");
    else cout << t << endl;

    return 0;
}

 A*算法

算法介绍

概念

        A*搜索算法(A* search algorithm)又称A*算法(A-star Algorithm),是比较流行的启发式搜索算法之一,被广泛应用于路径优化领域。

        A*算法结合了 Dijkstra 算法的优点(即保证找到最短路径)和贪心算法最佳优先搜索的优点(通过启发式函数引导搜索方向),在大多数情况下能高效地找到最优路径。

算法原理

        A*算法是一种结合了谈心的搜索算法,它的主要算法思想是,在当前节点优先扩展距离终点“近”的节点,但是我们也不知道哪个点到终点近呀,所以可以“猜”:走当前节点到终点距离(F) = 目前到当前节点的实际距离(g)+ 估计距离(h);核心公式:f(n)=g(n)+h(n)

        对这个距离使用小根堆进行排序优先扩展,优先走当前到终点较小的位置。这样第一个终点出队一定是最短距离,这样做就避免了搜索很多重复的路径,优化了BFS。

        估价函数的编写:A*算法的关键就是估价函数,编写A*估价函数有一个必要的条件:估计距离<=真实距离,当然越接近越好。我们在编写估价函数时可以参考古人的智慧,例如在不考虑障碍物的情况下,用哈曼顿距离,或者欧几里德距离。

注意:只有有解的情况比较优秀,无解时甚至不如普通的BFS,因为多了个优先队列的排序

 例题讲解

八数码

思路分析:用当前节点状态与目标状态的哈曼顿距离之和作为估价函数,套用A*算法模板即可

#include<bits/stdc++.h>
using namespace std;

typedef pair<int, string> PIS;
const int N = 1e3+10;
char cs[]="udlr";
int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};

int f(string state){
    //这里估价函数用的就是目标状态与现在状态的哈曼顿距离和
    int res = 0;
    for(int i = 0; i < state.size(); i++){
        if(state[i] != 'x'){
            int t = state[i]-'1';
            res += abs(i/2-t/3)+abs(i%3-t%3);
        }
    }
    return res;
}

string bfs(string start){
    string end = "12345678x";
    unordered_map<string,int> dist;
    unordered_map<string,pair<char,string>> prev; //记录路径
    priority_queue<PIS,vector<PIS>,greater<PIS>> heap; //按照总估价顺序去扩展点
    
    dist[start] = 0;
    heap.push({dist[start]+f(start),start});
    
    while(!heap.empty()){
        auto t = heap.top();
        heap.pop();
        
        string state = t.second;
        if(state == end) break;
        
        int k = state.find('x');
        int x = k/3,y = k%3;
        string source = state;
        for(int i = 0; i < 4; i++){
            for(int j = 0; j < 4; j++){
                int sx = x+dx[i],sy = y+dy[i];
                if(sx >= 0 && sy >= 0 && sy < 3 && sx < 3){
                    state = source; //还原状态
                    swap(state[k],state[sx*3+sy]);
                    if(!dist.count(state) || dist[state] > dist[source]+1){
                        dist[state] = dist[source]+1;
                        prev[state] = {cs[i],source};
                        heap.push({dist[state]+f(state),state});
                    }
                }
            }
        }
    }
    string res;
    while(end != start){
        res += prev[end].first;
        end = prev[end].second;
    }
    reverse(res.begin(),res.end());
    return res;
}

int main()
{
    string start,seq;
    char c;
    for(int i = 1; i <= 9; i++){
    	cin >> c;
        start += c;
        if(c != 'x') seq += c;
    }
    int cnt = 0;
    //奇妙做法判断是否有解,如果逆序对数量为奇数无解,否则有解。
    for(int i = 0; i < 8; i++){
        for(int j = i; j < 8; j++){
            if(seq[i] > seq[j]) cnt++;
        }
    }
    if(cnt%2 == 1) cout << "unsolvable" << endl;
    else cout << bfs(start) << endl;
    return 0;
}

第K短路

思路分析:这里既然是让求K短路,那么我们就有一个想法用最短路作为估价函数,因为他一定<=真实值,之后我们用一个cnt数组表示T被遍历过的次数,第K次即为第K短路

#include<bits/stdc++.h>
using namespace std;

#define x first
#define y second

typedef pair<int, int> PII;
typedef pair<int, PII> PIII;

const int N = 1010,M = 200010;
int n,m,S,T,K;
int h[N],rh[N],e[M],w[M],ne[M],dist[N],cnt[N],idx;
bool st[N];

void add(int h[],int a,int b,int c){
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx;
    idx++;
}

void dijkstra()  // 求1号点到n号点的最短路距离
{
    memset(dist, 0x3f, sizeof dist);
    dist[T] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, T});

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.y, dis = t.x;

        if (st[ver]) continue;
        st[ver] = true;

        for (int i = rh[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j], j});
            }
        }
    }
}

int astar(){
    priority_queue<PIII,vector<PIII>,greater<PIII>> heap;
    heap.push({dist[S],{0,S}});
    while(!heap.empty()){
        auto t = heap.top();
        heap.pop();
        int ver = t.y.y,dis = t.y.x;
        cnt[ver]++;
        if(cnt[T] == K) return dis; //记满K次,说明找到K短路了,是从第1,2...K的
        for(int i = h[ver]; i != -1; i = ne[i]){
            int j = e[i];
            if(cnt[j] < K){
            	heap.push({dis+w[i]+dist[j],{dis+w[i],j}});
			}
        }
    }
    return -1;
}

int main()
{
    memset(h, -1, sizeof h);
    memset(rh, -1, sizeof rh);
    scanf("%d%d", &n, &m);
    for(int i = 0; i < m; i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(h,a, b, c);
        add(rh,b,a,c);
    }
    scanf("%d%d%d", &S, &T,&K);
    if(S == T) K++; //每条最短路中至少要包含一条边。
    dijkstra();
    cout << astar() << endl;
    return 0;
}

 DFS之连通性模型

算法介绍

        跟bfs中的Flood Fil算法类似,都是解决连通问题。也都是进行四个方向扩展,只不过Dfs中是沿着一条路走到黑在一层层回溯去搜索,并且DFS不可以找到最短路,只可以看是否找到这个点。

例题讲解

迷宫

思路分析:很简单!!从当前节点开始扩展看是否能到达目标节点即可。

#include<bits/stdc++.h>
using namespace std;

const int N = 1e3+10;
char g[N][N];
bool st[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

int T,n,sx,sy,ex,ey; 

bool dfs(int x,int y){
    if(g[x][y] == '#') return 0; //非常坑,如果开始点有障碍物也不行
    if(x == ex && y == ey) return 1; //找到就返回1
    st[x][y] = 1;
    for(int i = 0; i < 4; i++){
        int a = x+dx[i],b = y+dy[i];
        if(a >= 0 && a < n && b >= 0 && b < n && !st[a][b] && g[a][b] != '#'){
            if(dfs(a,b)) return 1; //向四个方向扩展
        }
    }
    return 0;
}



int main()
{
    cin >> T;
    while(T--){
        cin >> n;
        for(int i = 0; i < n; i++) cin >> g[i];
        cin >> sx >> sy >> ex >> ey;
        memset(st, 0, sizeof st);
        if(dfs(sx,sy)) cout << "YES" << endl;
        else cout << "NO" << endl;
    }
    return 0;
}

 红与黑

思路分析:这个题也很简单,本身就是BFS的Flood Fil算法的模板题,从起始状态出发只要满足条件就继续扩展,最后看看有几个点被访问过即答案。

 

#include<bits/stdc++.h>
using namespace std;

const int N = 1e2+10;
int ans = 0;
char g[N][N];
bool st[N][N];
int n,m; 
int sx,sy;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

void bfs(int x,int y){
    st[x][y] = 1; //标记访问
    ans++; //数量++
    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 && g[a][b] == '.' && !st[a][b]) bfs(a,b);
    }
}

int main()
{
    while(cin >> m >> n,n != 0 && m != 0){
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                char c; cin >> c;
                if(c == '@'){
                    sx = i,sy = j;
                    g[i][j] = '.';
                }
                else g[i][j] = c;
            }
        }
        memset(st, 0, sizeof st);
        ans = 0;
        bfs(sx,sy);
        cout << ans << endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值