L - L Gym - 101498L(超级源点 + 最短路模板)

讲题之前,先纪念一下我的 vj 700题( ̄▽ ̄)~*,接下来向 800 题冲冲冲····
在这里插入图片描述

知识点:传送门

题意

  1. 给我们一个带有边权的有向图,边权可以为 正 或 负。
  2. 让我们求图中的图中的最短路,
  3. 注意有向图可能形成 负环,如果有负环输出 -inf。

思路

  1. 我们首先 虚拟出来一个超级源点 0, 让后让 0 点与 1~n 中的每个点都练接一条长 0 的有向边,
  2. 然后从 0 节点开始跑最短,因为存在负环所以不能用 dijkstar,可以用 spfa 来跑最短路,
    1. 如果图中存在负环,负环上的点会一会被放到队列 q 中,更新越来越小,这时候我们只需要统计某个点是否 进入队列超过 n 次(一点最不会被入队更新超过 n 次),如果是的话就有环,否则输出从超级源点 0 到其点的最小距离就行,
  3. 分析超级源点的作用,我们不能对每个点跑最短路 spfa 并判断负环,建立超级源点之后只需要跑一次就行了,
  4. 最后考虑所有边都是正权值的情况,这样建立超级源点跑完之后的答案为 0,是不符合题意的, 这个时候我们提前判断输出就行了。

bfs 版 spfa代码

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <string>
#include <queue>
#include <map>
#include <bitset>
#include <vector>
using namespace std;
void fre() { system("clear"), freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { system("clear"), freopen("A.txt", "r", stdin);}
void Run(int x = 0) {     
#ifdef ACM  //宏定义免注释 freopen
    if (! x) fre(); else Fre();
#endif
}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define db double
#define ll long long
#define ull unsigned long long
#define Pir pair<ll, ll>
#define m_p make_pair
#define for_(i, s, e) for(ll i = (ll)(s); i <= (ll)(e); i ++)
#define rep_(i, e, s) for(ll i = (ll)(e); i >= (ll)(s); i --)
#define memset(a, b, c) memset(a, (int)b, c);
#define size() size() * 1LL
#define sc scanf
#define pr printf
#define sd(a) sc("%lld", &a)
#define ss(a) sc("%s", a)
#define __  pr( "------------------------\n" );
#define ___ pr("\n------------------------\n");
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define esp 1e-7
#define mod (ll)(1e9 + 7)
/*=========================ACMer===========================*/
const int mxn = 5005;
int n, m;
struct Edge
{
    int v, w, nxt;
} edge[mxn << 1];
int head[mxn], tot;
int vis[mxn];
ll dis[mxn];
int tim[mxn];           //某个点入队/出队的次数

void init(int n)
{
    memset(head, 0, sizeof head);
    tot = 0;
}

void Add(int u, int v, int w)
{
    edge[++ tot] = (Edge){ v, w, head[u] };
    head[u] = tot;
}

bool spfa(int u)
{
    memset(vis, 0, sizeof vis);
    memset(dis, inf, sizeof dis);
    memset(tim, 0, sizeof tim);
    vis[u] = 1;
    dis[u] = 0;
    tim[u] = 1;
    queue<int> q;
    q.push(u);

    while (q.size())
    {
        u = q.front(); q.pop(); 
        vis[u] = 0;                         //u 出队列了,解除标记
        for (int i = head[u]; i; i = edge[i].nxt)
        {
            int v = edge[i].v;
            int w = edge[i].w;
            if (dis[v] > dis[u] + w)
            {
                dis[v] = dis[u] + 1LL * w;
                if (! vis[v])               //v 不在队列里面
                {
                    vis[v] = 1;
                    q.push(v);
                    tim[v] ++;
                    if (tim[v] > n) return 0;
                }
            }
        }
    }

    return 1;
}




int main()
{
    Run();
    int T; sc("%d", &T);
    while (T --)
    {
        sc("%d %d", &n, &m);
        init(n);
        int u, v, w; ll ans = INF;
        for_(i, 1, m)
        {
            sc("%d %d %d", &u, &v, &w);
            Add(u, v, w);        
            ans = min(ans, w * 1LL);
        }

        if (ans >= 0)
        {
            pr("%lld\n", ans);
            continue;
        }

        for_(i, 1, n)       //构造超级源点, 这样就不用对每个点都跑一次 spfa 了,  当时要特判断全是 正权边的情况
        {
            Add(0, i, 0);
        }

        if (! spfa(0))
        {
            pr("-inf\n");
            continue;
        }

        for_(i, 1, n)
        {
            ans = min(ans, dis[i]);
        }

        pr("%lld\n", ans);
    }


    return 0;
}

dfs 版 spfa

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <string>
#include <queue>
#include <map>
#include <bitset>
#include <vector>
using namespace std;
void fre() { system("clear"), freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { system("clear"), freopen("A.txt", "r", stdin);}
void Run(int x = 0) {     
#ifdef ACM  //宏定义免注释 freopen
    if (! x) fre(); else Fre();
#endif
}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define db double
#define ll long long
#define ull unsigned long long
#define Pir pair<ll, ll>
#define m_p make_pair
#define for_(i, s, e) for(ll i = (ll)(s); i <= (ll)(e); i ++)
#define rep_(i, e, s) for(ll i = (ll)(e); i >= (ll)(s); i --)
#define memset(a, b, c) memset(a, (int)b, c);
#define size() size() * 1LL
#define sc scanf
#define pr printf
#define sd(a) sc("%lld", &a)
#define ss(a) sc("%s", a)
#define __  pr( "------------------------\n" );
#define ___ pr("\n------------------------\n");
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define esp 1e-7
#define mod (ll)(1e9 + 7)
/*=========================ACMer===========================*/
const int mxn = 5005;
struct Edge
{
    int v, w, nxt;
} edge[mxn << 1];
int head[mxn], tot;
bool circle;
int vis[mxn];
int dis[mxn];

void init(int n)
{
    memset(head, 0, sizeof head);
    for_(i, 0, n) dis[i] = inf, vis[i] = 0;
    tot = 0;
    circle = 0;
}

void Add(int u, int v, int w)
{
    edge[++ tot] = (Edge){ v, w, head[u] };
    head[u] = tot;
}

void spfa(int u)
{
    if (circle) return;
    vis[u] = 1;
    for (int i = head[u]; i; i = edge[i].nxt)
    {
        int v = edge[i].v;
        int w = edge[i].w;
        if (dis[v] > dis[u] + w)            //如果可以松弛
        {
            dis[v] = dis[u] + w;
            if (! circle && vis[v])         //如果可以松弛,而且松弛的 v 节点是已经访问过的节点,说明存在负环因为之后存在 负环 才能松弛当前路径已访问过的节点
            {
                circle = 1;
                return;
            }
            else
            {
                spfa(v);
            }
        }
    }
    vis[u] = 0;                 //回溯解除标记
}




int main()
{
    Run();
    int T; sc("%d", &T);
    while (T --)
    {
        int n, m;
        sc("%d %d", &n, &m);
        init(n);
        int u, v, w, ans = inf;
        for_(i, 1, m)
        {
            sc("%d %d %d", &u, &v, &w);
            Add(u, v, w);        
            ans = min(ans, w);
        }

        if (ans >= 0)
        {
            pr("%d\n", ans);
            continue;
        }

        for_(i, 1, n)       //构造超级源点
        {
            Add(0, i, 0);
        }

        dis[0] = 0;
        spfa(0);

        if (circle)
        {
            pr("-inf\n");
            continue;
        }

        for_(i, 1, n)
        {
            ans = min(ans, dis[i]);
        }
        pr("%d\n", ans);
    }


    return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值