acwing刷题指南8

搜索:

dfs

842. 排列数字

843. n-皇后问题(类似于全排列)

846. 树的重心

bfs

844. 走迷宫

847. 图中点的层次

848. 有向图的拓扑序列

最短路算法:

acwing刷题指南8_第1张图片

849. Dijkstra求最短路 I

850. Dijkstra求最短路 II

851. spfa求最短路

文字叙述:

Dijkstra-朴素O(n^2)

初始化距离数组, dist[1] = 0, dist[i] = inf;
for n次循环 每次循环确定一个min加入S集合中,n次之后就得出所有的最短距离
将不在S中dist_min的点->t
t->S加入最短路集合
用t更新到其他点的距离
Dijkstra-堆优化O(mlogm)

利用邻接表,优先队列
在priority_queue[HTML_REMOVED], greater[HTML_REMOVED] > heap;中将返回堆顶
利用堆顶来更新其他点,并加入堆中类似宽搜
Bellman_fordO(nm)

注意连锁想象需要备份, struct Edge{inta,b,c} Edge[M];
初始化dist, 松弛dist[x.b] = min(dist[x.b], backup[x.a]+x.w);
松弛k次,每次访问m条边


Spfa O(n)~O(nm)

利用队列优化仅加入修改过的地方
for k次
for 所有边利用宽搜模型去优化bellman_ford算法
更新队列中当前点的所有出边
Floyd O(n^3)

初始化d
k, i, j 去更新d

842. 排列数字

给定一个整数 n,将数字1∼n 排成一排,将会有很多种排列方法。

现在,请你按照字典序将所有的排列方法输出。

输入格式

共一行,包含一个整数 n。

输出格式

按字典序输出所有排列方案,每个方案占一行。

数据范围

1≤n≤7

输入样例:

3

输出样例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

acwing刷题指南8_第2张图片

//第一种写法

#include
using namespace std;
const int N = 10;
int n,path[N];//path[ ]表示是存的这条路径
bool vis[N];//vis[ ]表示判断这条路是否搜过
void dfs(int u)
{
    if(u == n)
    {
        for(int i = 0;i < n;i++)cout<         puts("");
        return;
    }
    for(int i = 1;i <= n;i++)
        if(!vis[i])
        {
          path[u] = i;//将这条未被搜到的路线存进去
          vis[i] = true;//标记一下
          dfs(u+1);//继续搜下一个点
          vis[i] = false;//回溯
        }
}
int main()
{
    cin>>n;
    dfs(0);
    return 0;
}

//第二种写法

#include
using namespace std;
const int N = 10;
int n;
int path[N];
bool vis[N];

void dfs(int u,int state)
{
    if(u == n)
    {
        for(int i = 0;i < n;i++)cout<         puts("");
        return;
    }
    for(int i = 0;i < n;i++)
    if(!(state>>i&1))
    {
        path[u] = i+1;//跳过
        dfs(u+1,state+(1<     }
}

//考虑state的二进制表示,如果第i位是1,表示当前数已经被用过了,否则表示没被用过。所以如果i已经被用过了,则需要跳过

int main()
{
    cin>>n;
    dfs(0,0);
    return 0;
}

acwing刷题指南8_第3张图片

 第二种方法更快

843. n-皇后问题

n−皇后问题是指将 n 个皇后放在 n×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

acwing刷题指南8_第4张图片

现在给定整数 n,请你输出所有的满足条件的棋子摆法。

输入格式

共一行,包含整数 n。

输出格式

每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。

其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。

每个方案输出完成后,输出一个空行。

注意:行末不能有多余空格。

输出方案的顺序任意,只要不重复且没有遗漏即可。

数据范围

1≤n≤9

输入样例:

4

输出样例:

.Q..
...Q
Q...
..Q.

..Q.
Q...
...Q
.Q..

acwing刷题指南8_第5张图片

PS:这是别人写的,给大家参考一下思路 

#include
using namespace std;
const int N = 20;
int n;
char g[N][N];
bool hp[N],dg[N],udg[N];//hp[ ]表示横排,dg[ ]表示斜排(对角线)udg[ ]表示反对角线

//这里的对角线可以理解为生物遗传学9:3:3:1里面那张图

void dfs(int u)
{
    if(u == n)
    {
        for(int i = 0;i < n;i++)puts(g[i]);
        puts("");
        return;
    }
    for(int i = 0;i < n;i++)
    if(!hp[i] && !dg[u+i] && !udg[n - u + i])
    {
        g[u][i] = 'Q';
        hp[i] = dg[u+i] = udg[n-u+i] = true;
        dfs(u+1);
        hp[i] = dg[u+i] = udg[n-u+i] = false;
        g[u][i] = '.';
        
    }

  // 剪枝(对于不满足要求的点,不再继续往下搜索)  
    
}

int main()
{
    cin>>n;
    for(int i = 0;i < n;i++)
    for(int j = 0;j < n;j++)
    g[i][j] = '.';
    dfs(0);
    return 0;
}

844. 走迷宫

给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。

最初,有一个人位于左上角 (1,1)处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。

请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。

数据保证 (1,1) 处和 (n,m)处的数字为 0,且一定至少存在一条通路。

输入格式

第一行包含两个整数 n 和 m。

接下来 n 行,每行包含 m 个整数(0 或 1),表示完整的二维数组迷宫。

输出格式

输出一个整数,表示从左上角移动至右下角的最少移动次数。

数据范围

1≤n,m≤100

输入样例:

5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0

输出样例:

8

详细看代码吧

手写的:

#include
#include
#include
using namespace std;
const int N = 1e2+8;
typedef pair PII;
int g[N][N],d[N][N];//g[ ][ ]存放地图,d[ ][ ]记录每一个点到起点的距离
PII q[N * N];//手写队列
int n,m;
int bfs()
{
    int hh = 0,tt = 0;//将(0,0)这个点覆盖了,本来tt = -1的
    q[0] = {0,0};
    memset(d,-1,sizeof d);//全部标记为-1 (就是没走过的)
    d[0][0] = 0;//表示起点走过了
    int dx[4] = {-1,0,1,0},dy[4] = {0,1,0,-1};//x 方向的向量和 y 方向的向量组成的上、右、下、左
    while(hh <= tt)
    {
        PII t = q[hh++];
        for(int i = 0;i < 4;i++)
        {
            int x = t.first + dx[i],y = t.second + dy[i];//x表示沿着此方向走会走到哪个点
            if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1)//在边界内 并且是空地可以走 且之前没有走过
            {
                d[x][y] = d[t.first][t.second] + 1;//到起点的距离
                q[++tt] = {x,y};//新坐标入队
                
            }
        }
    }
    return d[n - 1][m - 1]; //输出右下角点距起点的距离即可
}
int main()
{
    cin>>n>>m;
    for(int i = 0;i < n;i++)
    for(int j = 0;j < m;j++)
    cin>>g[i][j];
    cout<     return 0;
}

时间:26s

利用STL的:

#include
#include
#include

using namespace std;

const int N = 110;

typedef pair PII;

int n, m;
int g[N][N], d[N][N];

int bfs()
{
    queue< pair > q;

    q.push({0, 0});

    memset(d, -1, sizeof(d));

    d[0][0] = 0;


    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

    while (q.size())//队列不为空
    {
        PII 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 && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1)
            {
                d[x][y] = d[t.first][t.second] + 1;//当前点到起点的距离
                q.push({x, y});//将新坐标入队
            }
        }
    }

    return d[n - 1][m -1];
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cin >> g[i][j];

    cout << bfs() << endl;

    return 0;
}

时间:30s

846. 树的重心

给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1条无向边。

请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

输入格式

第一行包含整数 n,表示树的结点数。

接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。

输出格式

输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。

数据范围

1≤n≤10^5

输入样例

9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6

输出样例:

4

#include
#include
#include
using namespace std;

const int N = 100010;
int h[N],val[N * 2],ne[N * 2],idx;
int n;
bool st[N];
int ans = N;//答案记录的是最小的最大值
void add(int a,int b)
{
    val[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

//插入还是用单链表插法

int dfs(int u)
{
    int sum = 1,res = 0;//sum表示的是当前子树的大小,因为有一个u了所以为1    res表示的是把这个元素删除后,联通快最大的数值
    st[u] = true;
    for(int i = h[u];i!=-1;i = ne[i])
    {
        int j = val[i];
        if(!st[j]) 
        {
          int s = dfs(j);//s表示当前子树的大小
          res = max(res,s);
          sum += s;
        }
    }
     res = max(res,n-sum);
     ans = min(ans,res);
     return sum;
}
int main()
{
    cin>>n;
    memset(h,-1,sizeof h);
    for(int i = 0;i < n-1;i++)

//for(int i = 1;i <= n;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);
        add(b,a);
    }
    dfs(1);
    cout<     return 0;
}

847. 图中点的层次

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环。

所有边的长度都是 1,点的编号为 1∼n。

请你求出 1号点到 n 号点的最短距离,如果从 1 号点无法走到 n 号点,输出 −1。

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 a 和 b,表示存在一条从 a 走到 b 的长度为 1 的边。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。

数据范围

1≤n,m≤10^5

输入样例:

4 5
1 2
2 3
3 4
1 3
1 4

输出样例:

1

acwing刷题指南8_第6张图片

#include
#include
#include
using namespace std;
const int N = 100010;
int ne[N],val[N],idx,h[N];
int n,m;//n表示点,m表示边
int d[N],q[N];//d表示距离,q表示队列
void add(int a,int b)
{
    val[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++; 
}
int bfs()
{
    int hh = 0,tt = 0;
    q[0] = 1;
    memset(d,-1,sizeof d);//表示这些点都没被遍历过
    d[1] = 0;//表示第一个点被遍历过了
    while(hh <= tt)
    {
        int t = q[hh++];//先取队头
        for(int i = h[t];i != -1;i = ne[i])//扩展邻边,因为权值都为1,所以用bfs搜能搜到最短路
        {
            int j = val[i];
            if(d[j] == -1)
        {
            d[j] = d[t] + 1;
            q[++tt] = j;
    }
    }
    }
        return d[n];
    }
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i = 1;i <= m;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    cout<     
    return 0;
}

848. 有向图的拓扑序列//重点useful

给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。

请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1−1。

若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 AA 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 x 和 y,表示存在一条从点 x 到点 y 的有向边 (x,y)。

输出格式

共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。

否则输出 −1。

数据范围

1≤n,m≤10^5

输入样例:

3 3
1 2
2 3
1 3

输出样例:

1 2 3

啥是拓扑排序?

一个有向图,如果图中有入度为 0 的点,就把这个点删掉,同时也删掉这个点所连的边。

一直进行上面出处理,如果所有点都能被删掉,则这个图可以进行拓扑排序。

For example:

acwing刷题指南8_第7张图片

开始时,图是这样的状态,发现A的入度为 0,所以删除A和A上所连的边,结果如下图:

acwing刷题指南8_第8张图片

这时发现B的入度为 0,C的入度为 0,所以删除B和B上所连的边、C和C上所连的边,结果如下图:

acwing刷题指南8_第9张图片

这时发现发现D的入度为 0,所以删除D和D上所连的边(如果有就删)

这时整个图被删除干净,所有能进行拓扑排序。

所以由上面可以得出这道题的思路:

首先记录各个点的入度

然后将入度为 0 的点放入队列

将队列里的点依次出队列,然后找出所有出队列这个点发出的边,删除边,同事边的另一侧的点的入度 -1。

如果所有点都进过队列,则可以拓扑排序,输出所有顶点。否则输出-1,代表不可以进行拓扑排序。

当然,也可以用小根堆

#include
#include
#include
using namespace std;
const int N = 100010;
int n,m;
int ne[N],val[N],idx,h[N];//我们可以发现,只要是跟树和图有关,这一行代码不少见
int d[N],q[N]; //d[ ]我们这里表示度(入度) 
void add(int a,int b)
{
    val[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}
bool topsort()
{
    int hh = 0,tt = -1;
    
    for(int i = 1;i <= n;i++)
        if(!d[i])q[++tt] = i;//将入度为零的点入队
    while(hh <= tt)
    {
        int t = q[hh++];
        for(int i = h[t];i != -1;i = ne[i])
        {
            int j = val[i];
            //d[j]--;   //删除点t指向点j的边
            //if(d[j]==0)   //如果点j的入度为零了,就将点j入队

            
            
            if(--d[j] == 0)//先把d[j]--后续判断是否为0
            q[++tt] = j; 
        }
    }
    return tt == n-1;//这里运用了鸽巢原理 
}
int main()
{
    cin>>n>>m;//n表示点数,m表示边数
    memset(h,-1,sizeof h);
    for(int i = 0;i < m;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);
        d[b]++;//因为是a指向b,所以b点的入度要加1
        
        
    }
    if(topsort())
    {
        for(int i = 0;i < n;i++)cout<         puts("");
    }
    else puts("-1");
    return 0;
}
//本题答案并不唯一 

849. Dijkstra求最短路 I

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。

输入格式

第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 −1。

数据范围

1≤n≤500
1≤m≤10^5
图中涉及边长均不超过10000。

输入样例:

3 3
1 2 2
2 3 1
1 3 4

输出样例:

3

Dijkstra 的整体思路比较清晰


进行n(n为n的个数)次迭代去确定每个点到起点的最小值 最后输出的终点的即为我们要找的最短路的距离

所以按照这个思路除了存储图外我们还需要存储两个量

dist[n] //用于存储每个点到起点的最短距离
st[n]   //用于在更新最短距离时 判断当前的点的最短距离是否确定 是否需要更新

每次迭代的过程中我们都先找到当前未确定的最短距离的点中距离最短的点
(至于为什么是这样那么这就涉及到Dijkstra算法的具体数学证明了 有兴趣的同学可以百度一下)

int t=-1;       //将t设置为-1 因为Dijkstra算法适用于不存在负权边的图
for(int j=1;j<=n;j++)
{
    if(!st[j]&&(t==-1||dist[t]>dist[j])    //该步骤即寻找还未确定最短路的点中路径最短的点
        t=j;
}
通过上述操作当前我们的 t 代表就是剩余未确定最短路的点中 路径最短的点
而与此同时该点的最短路径也已经确定我们将该点标记

st[t]=true;

然后用这个去更新其余未确定点的最短距离

for(int j=1;j<=n;j++)
    dist[j]=min(dist[j],dist[t]+g[t][j]);

 

进行n次迭代后最后就可以确定每个点的最短距离
然后再根据题意输出相应的 要求的最短距离

#include
#include
#include
using namespace std;
const int N = 510;
int g[N][N],dis[N],n,m;//为稠密阵所以用邻接矩阵存储
bool st[N];

int dij()
{
	memset(dis,0x3f,sizeof dis);
	dis[1] = 0; 
	for(int i = 0;i < n;i++)
	{
	    int t = -1;//因为dij算法不存在负权边
	    for(int j = 1;j <= n;j++) //依次更新每个点所到相邻的点路径值
	        if(!st[j] && (t == -1 || dis[t] > dis[j]))
	        t = j;
	        
	        st[t] = true;
	        
	        for(int j = 1;j <= n;j++)
	        dis[j] = min(dis[j],dis[t]+g[t][j]);
	}
	
	if(dis[n] == 0x3f3f3f3f)return -1; //如果第n个点路径为无穷大即不存在最低路径
	return dis[n];
	
}
int main()
{
	cin>>n>>m;
	memset(g,0x3f,sizeof g);
	//处理重边情况
	while(m--)
	{
	    int x,y,z;
	    cin>>x>>y>>z;
	    g[x][y] = min(g[x][y],z);
	}
	cout<

850. Dijkstra求最短路 II

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。

输入格式

第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 −1。

数据范围

1≤n,m≤1.5×10^5
图中涉及边长均不小于 0,且不超过 10000。
数据保证:如果最短路存在,则最短路的长度不超过 109。

输入样例:

3 3
1 2 2
2 3 1
1 3 4

输出样例:

3

堆优化版的dijkstra是对朴素版dijkstra进行了优化,在朴素版dijkstra中时间复杂度最高的寻找距离
最短的点O(n^2)可以使用最小堆优化。


1. 一号点的距离初始化为零,其他点初始化成无穷大。
2. 将一号点放入堆中
3. 不断循环,直到堆空。每一次循环中执行的操作为:
    弹出堆顶(与朴素版diijkstra找到S外距离最短的点相同,并标记该点的最短路径已经确定)。
    用该点更新临界点的距离,若更新成功就加入到堆中。

图解:

acwing刷题指南8_第10张图片

acwing刷题指南8_第11张图片

#include
#include
#include
#include
using namespace std;
typedef pairPII;
const int N = 150010;
int dis[N];
int ne[N],val[N],idx,h[N],w[N];
//邻接表不需要对重边做处理了,有重边也不要紧,假设1->2有权重为2和3的边,再遍历到点1的时候2号点的距离会更新两次放入堆中
// 这样堆中会有很多冗余的点,但是在弹出的时候还是会弹出最小值2+x(x为之前确定的最短路径),
// 并标记st为true,所以下一次弹出3+x会continue不会向下执行。   
//w[ ]用来存权重
bool st[N];// 如果为true说明这个点的最短路径已经确定
int n,m;
void add(int a,int b,int c)
{
    val[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}
int dij()
{
    memset(dis,0x3f3f3f3f,sizeof dis);
    dis[1] = 0;
    
    priority_queue,greater> heap;//用STL来定义小根堆
     // 这里heap中为什么要存pair呢,首先小根堆是根据距离来排的,所以有一个变量要是距离,
    // 其次在从堆中拿出来的时候要知道知道这个点是哪个点,不然怎么更新邻接点呢?所以第二个变量要存点。
    heap.push({0,1});// 这个顺序不能倒,pair排序时是先根据first,再根据second,这里显然要根据距离排序
    
    while(heap.size())
    {
        PII t = heap.top();// 取不在集合S中距离最短的点
        heap.pop();
        
        int ver = t.second,distance = t.first;
        if(st[ver]) continue;//说明这个点是冗余备份,不用管这个点了
        st[ver] = true;
        
        //运用当前这个点更新邻边的点即可(既然是邻接表来存,那就用最常用的方法)
        for(int i = h[ver];i != -1;i = ne[i])
        {
            int j = val[i];
            if(dis[j] > distance + w[i])
            {
                dis[j] = distance + w[i];
                heap.push({dis[j],j});
                
            }
        }
    }
    
    if(dis[n]  == 0x3f3f3f3f)return -1;
    return dis[n];
    
 } 
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }
    
    cout<

851. spfa求最短路(spfa跟dij优化版有点像)

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible

数据保证不存在负权回路。

输入格式

第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示 1号点到 n 号点的最短距离。

如果路径不存在,则输出 impossible

数据范围

1≤n,m≤10^5
图中涉及边长绝对值均不超过 10000。

输入样例:

3 3
1 2 5
2 3 -3
1 3 4

输出样例:

2

值得注意的是
1) st数组的作用:判断当前的点是否已经加入到队列当中了;已经加入队列的结点就不需要反复的把该点加入到队列中了,就算此次还是会更新到源点的距离,那只用更新一下数值而不用加入到队列当中。
即便不使用st数组最终也没有什么关系,但是使用的好处在于可以提升效率。
2) SPFA算法看上去和Dijstra算法长得有一些像但是其中的意义还是相差甚远的:

1] Dijkstra算法中的st数组保存的是当前确定了到源点距离最小的点,且一旦确定了最小那么就不可逆了(不可标记为true后改变为false);SPFA算法中的st数组仅仅只是表示的当前发生过更新的点,且spfa中的st数组可逆(可以在标记为true之后又标记为false)。顺带一提的是BFS中的st数组记录的是当前已经被遍历过的点。
2] Dijkstra算法里使用的是优先队列保存的是当前未确定最小距离的点,目的是快速的取出当前到源点距离最小的点;SPFA算法中使用的是队列(你也可以使用别的数据结构),目的只是记录一下当前发生过更新的点。

3) Bellman_ford算法里最后return -1的判断条件写的是dist[n]>0x3f3f3f3f/2; 

而spfa算法写的是dist[n]==0x3f3f3f3f;  

其原因在于Bellman_ford算法会遍历所有的边,因此不管是不是和源点连通的边它都会得到更新;但是SPFA算法不一样,它相当于采用了BFS,因此遍历到的结点都是与源点连通的,因此如果你要求的n和源点不连通,它不会得到更新,还是保持的0x3f3f3f3f。

4) Bellman_ford算法可以存在负权回路,是因为其循环的次数是有限制的因此最终不会发生死循环;但是SPFA算法不可以,由于用了队列来存储,只要发生了更新就会不断的入队,因此假如有负权回路请你不要用SPFA否则会死循环。

5) 由于SPFA算法是由Bellman_ford算法优化而来,在最坏的情况下时间复杂度和它一样即时间复杂度为 O(nm) ,假如题目时间允许可以直接用SPFA算法去解Dijkstra算法的题目。

#include
#include
#include
#include
using namespace std;
typedef pairPII;
const int N = 100010;
int dis[N];
int ne[N],val[N],idx,h[N],w[N];//邻接表不需要对重边做处理了
bool st[N];
int n,m;
void add(int a,int b,int c)
{
    val[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}
int spfa()
{
    memset(dis,0x3f3f3f3f,sizeof dis); 
    dis[1] = 0;
    queueq;
    q.push(1);
    st[1] = true;//这里指的是当前这个点是否在队列当中
    
    while(q.size())
    {
        int t = q.front();
        q.pop();
        st[t] = false;
        
        for(int i = h[t];i != -1;i = ne[i])
        {
            int j = val[i];
            if(dis[j] > dis[t] + w[i])
            {
                dis[j] =dis[t] + w[i];
            if(!st[j])//这里我们需要判断是否j这个元素在队列里面了
            {
                q.push(j);//满足条件把j加进去
                st[j] = true;
            }
            
        }
    }
}
	return dis[n];
 } 
int main()
{
	cin>>n>>m;
	memset(h,-1,sizeof h);
	while(m--)
	{
	    int a,b,c;
	    cin>>a>>b>>c;
	    add(a,b,c);
	}
	
	int t = spfa();
	if(t == 0x3f3f3f3f)puts("impossible");
	else cout<

你可能感兴趣的:(OI新手入门刷题,学习笔记,c++,深度优先)