最短路板子

这篇博客介绍了图论中的最短路问题,包括单源最短路算法如Dijkstra和Bellman-Ford,以及它们在处理正权和负权边时的应用。Dijkstra不适用于负权边,而Bellman-Ford能处理含有负权回路的图。同时,文章还提到了SPFA算法,用于负权图的最短路径计算,并可用于检测负权环。

初学图论,先更最短路,后续更些基础题缓缓。

先说单源最短路中求只有正权的。

先说dijkstra,一定不能用于有负权边的!!!正权算法完全基于贪心,每个点只拓展一次,且找当前dist最小的点,找到一次就解决一个点的最短路(见下);之所以dijkstra不能用于负权,因权值可能为负,每个点可能需要多次拓展所以最坏情况会很慢(可能后面再拓展时由于负权边使dist大的点去拓展dist小的点)。

1 ACW849

先是朴素dij,不妨先将点分为两部分:S集合(包含已确定最短路的点)和T集合(包含还未确定最短路的点)。主要思路是n个点,每个点只扩展1次求得所有点的最短路,以下方法可以保证每个点只扩展一次(因为每个点拓展一次后就确定了,将它加入S,再也用不到它扩展了):每次从T集合中的所有点中选出一个距离当前源点距离dist最短的点,该点一定是所有点中的源点到其距离dist最短的点(画个图很好理解,边无负权,没有再通过更新其它点可以使该点dist变小),所以将该点标记为true,再用该加入S的点为先决条件遍历是否可以更新还未确定到源点的当前最短距离(即在T集合中的点的最短距离)。

//朴素dij   O(n^2)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=550;
int dist[N],n,m;
bool st[N];
int g[N][N];//稠密图
void dijkstra()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    for(int i=1;i<=n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++)
        {
            if(st[j]==false && (t==-1 || dist[j]>dist[t]))
                t=j;
        }
        st[t]=true;
        for(int j=1;j<=n;j++)
        {
            if(dist[j]>dist[t]+g[t][j])
                dist[j]=dist[t]+g[t][j];
        }
    }
    if(dist[n]==0x3f3f3f3f)
        return -1;
    return dist[n];
}
int main()
{
    cin>>n>>m;
    memset(g,0x3f,sizeof g);
    while (m -- )
    {
        int x,y,z;
        cin>>x>>y>>z;
        if(g[x][y]>z)
            g[x][y]=z;//若有多条重复边选权值最小的
    }
    cout<<dijkstra();
    return 0;
}

2 ACW850

优先队列模拟一个小根堆始终保持堆顶最小,所以不需要找当前dist最小的点,任务只剩在邻接表中对应的某一链表中更新其中点的dist(关于邻接表画个图很易懂):

//堆优化dij   O(mlogn)
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
const int M=2e5+10;
const int INF=0x3f3f3f3f;
int dist[N],n,m;
bool st[N];
int e[M],ne[M],w[M],h[N],idx;//以边数和点数开的数组是要分开的
void add(int a,int b,int c)//加入邻接表
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
int dijkstra()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    priority_queue<PII,vector<PII>,greater<PII> >heap;//小根堆,每插入一个元素排次序,始终保持堆顶最小
    heap.push({0,1});
    while(heap.size())
    {
        PII t=heap.top();
        heap.pop();
        int id=t.second,dis=t.first;
        if(st[id]==true)
            continue;
        st[id]=true;
        for(int i=h[id];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dis+w[i])
            {
                dist[j]=dis+w[i];
                heap.push({dist[j],j});
            }
        }
    }
    if(dist[n]==0x3f3f3f3f)
        return -1;
    return dist[n];
}
int main()
{
    cin>>n>>m;
    memset(h, -1, sizeof h);//千万不能丢!!!邻接表中每个链表的头结点初始指向-1表示以此点为起点的链表为空
    while (m -- )
    {
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
    }
    cout<<dijkstra();
    return 0;
}

单源最短路开始有负权了。
在负权边中,所谓找最短路的前提是DAG,有环的图中找最短路没意义。

3 ACW853

Bellman—Ford只走规定走k步时才用,一般限制走k步是因为图中有负权回路,若不限制步数会死循环。
①不需存储图,开个结构体存边即可。
②共k步,每一步用bak做最新dist的备份数组。即由于k步的限制,更新时只能用上一次的结果,可防止发生“串联”(画个图很易懂,即用刚更新的dist值又更新其它点的dist,会乱)。

//Bellman-Ford   O(mn)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=550;
const int M=1e5+10;
const int INF=0x3f3f3f3f;
int dist[N],n,m,k,bak[N];
bool st[N];
struct bian
{
    int x,y,z;
}s[M];
void bellmanford()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    while(k--)
    {
        memcpy(bak,dist,sizeof dist);
        for(int i=1;i<=m;i++)
        {
            int a=s[i].x,b=s[i].y,c=s[i].z;
            if(dist[b]>bak[a]+c)
                dist[b]=bak[a]+c;
        }
    }
}
int main()
{
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        s[i].x=a,s[i].y=b,s[i].z=c;
    }
    bellmanford();
    if(dist[n]>INF/2)//有负权,不一定是INF了
        cout<<"impossible"<<endl;
    else
        cout<<dist[n]<<endl;
    return 0;
}

4 ACW851

SPFA比较类似于BFS。要找到负权图最短路径,类比堆优化dij,最直接的办法是解除每个点只扩展一次的限制,扩展一次进一次堆排个序再找堆顶,只要堆不为空一直取堆顶。但此时取堆顶元素并没有意义(如果过几步又是这个点扩展的可能更小),而堆每次排序还要O(logn),所以干脆不排序了(不排序了pair里的dist没意义了,直接存编号更好),直接用queue,时间复杂度还能降到O(1)。此时,判断该点是否已拓展过的bool数组便失去意义可删去,且queue队列里,同一个点在数组里也没意义,所以新加一个bool数组并含义为“判断当前queue中是否有两个相同元素,有则跳过”。

//SPFA   O(m)~O(n*m)
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N=1e5+10;
const int M=1e5+10;
const int INF=0x3f3f3f3f;
int dist[N],n,m;
bool st[N];
int e[M],ne[M],w[M],h[N],idx;
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
void spfa()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    queue<int>q;//只存下标
    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=e[i];
            if(dist[j]>dist[t]+w[i])
            {
                dist[j]=dist[t]+w[i];
                if(st[j]==false)
                {
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
}
int main()
{
    cin>>n>>m;
    memset(h, -1, sizeof h);
    while (m -- )
    {
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
    }
    spfa();
    if(dist[n]>INF/2)
        cout<<"impossible";
    else
        cout<<dist[n];
    return 0;
}

除此之外,SPFA常用于判断是不是有负权环。
用一个cnt数组记录到当前点时已经走了几步,若cnt初值为0,找到cnt>=n,则说明n个点中一定有两个点被重复走过了,必然存在负权环。需要注意,负环可能由某一点到不了(假如图中有多个连通分量,只有其中一个有环),所以需要将所有n个点都加入队列中进行初始化。

5 ACW852

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N=2e3+10;
const int M=1e4+10;
const int INF=0x3f3f3f3f;
int dist[N],n,m,cnt[N];
bool st[N];
int e[M],ne[M],w[M],h[N],idx;
void add(int a,int b,int c)
{
    w[idx]=c;
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
void f()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    queue<int>q;
    for(int i=1;i<=n;i++)
    {
        q.push(i);
        st[i]=true;
    }
    while(q.size())
    {
        int t=q.front();
        st[t]=false;
        q.pop();
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[t]+w[i])
            {
                dist[j]=dist[t]+w[i];
                cnt[j]=cnt[t]+1;
                if(st[j]==false)
                {
                    q.push(j);
                    st[j]=true;
                }
            }
            if(cnt[j]>n-1)
            {
                cout<<"Yes";
                return;
            }
        }
    }
    cout<<"No";
}
int main()
{
    cin>>n>>m;
    memset(h, -1, sizeof h);
    while (m -- )
    {
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
    }
    f();
    return 0;
}

以上都是求单源最短路,下面求多源最短路。

6 ACW854

//Floyd   O(n^3)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=220;
const int INF=0x3f3f3f3f;
int g[N][N];
int n,m,k;
void floyd()
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(g[i][j]>g[i][k]+g[k][j])
                    g[i][j]=g[i][k]+g[k][j];
}
int main()
{
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(i==j)
                g[i][j]=0;
            else
                g[i][j]=INF;
        }
    }
    while (m -- )
    {
        int x,y,z;
        cin>>x>>y>>z;
        if(g[x][y]>z)
            g[x][y]=z;
    }
    floyd();
    while(k--)
    {
        int x,y;
        cin>>x>>y;
        if(g[x][y]>INF/2)
            cout<<"impossible"<<endl;
        else
            cout<<g[x][y]<<endl;
    }
    return 0;
}
由于没有提供具体参考引用,以下是关于野火板子烧录的通用信息。 ### 烧录方法 #### ST - Link 烧录 ST - Link 是一种常用的烧录工具,支持 STM32 系列芯片的烧录。 1. 硬件连接:将 ST - Link 调试器的 SWD 接口与野火板子上对应的 SWD 接口连接,一般包括 SWCLK(时钟线)、SWDIO(数据线)、GND(地线)和 VCC(电源线)。 2. 软件配置:安装 ST - Link 驱动,打开烧录软件如 STM32CubeProgrammer 或 Keil MDK。在软件中选择正确的芯片型号,配置烧录参数,如波特率、烧录模式等。 3. 烧录操作:选择要烧录的固件文件(通常为.hex 或.bin 格式),点击烧录按钮,软件会自动将固件写入芯片。 ```python # 示例代码仅为示意,非实际运行代码 # 假设使用 Python 和 PySTLink 库进行烧录 import pystlink # 初始化 ST - Link stlink = pystlink.STLink() # 连接到目标芯片 stlink.connect() # 打开固件文件 with open('firmware.hex', 'rb') as f: firmware = f.read() # 烧录固件 stlink.flash(firmware) # 断开连接 stlink.disconnect() ``` #### J - Link 烧录 J - Link 也是一款功能强大的调试和烧录工具。 1. 硬件连接:将 J - Link 调试器的 JTAG 或 SWD 接口与野火板子连接,确保引脚对应正确。 2. 软件配置:安装 J - Link 驱动和相关烧录软件,如 Segger J - Flash。在软件中设置芯片型号和烧录配置。 3. 烧录操作:导入固件文件,点击烧录选项,完成烧录过程。 #### USB 串口烧录 部分野火板子支持通过 USB 串口进行烧录。 1. 硬件连接:使用 USB 线将野火板子与电脑连接,确保板子处于正确的烧录模式。 2. 软件配置:安装串口驱动,使用串口烧录工具如 FlyMcu。设置串口参数和固件文件。 3. 烧录操作:点击开始烧录按钮,烧录工具会通过串口将固件传输到板子。 ### 注意事项 1. 硬件连接:确保烧录工具与野火板子的连接正确,引脚无松动或短路。在连接前,检查电源和接地情况,避免损坏芯片。 2. 芯片型号:在烧录软件中选择正确的芯片型号,否则可能导致烧录失败或芯片损坏。 3. 固件文件:确保使用的固件文件与目标芯片和板子兼容,文件格式正确。 4. 烧录模式:有些板子需要进入特定的烧录模式才能进行烧录,如通过跳线或按键设置。在烧录前,确认板子处于正确的烧录模式。 5. 电源供应:烧录过程中,保证板子有稳定的电源供应,避免因电源波动导致烧录中断。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值