算法竞赛进阶指南——0x61【最短路】

先来点涩图给xd助助兴⭐♥
在这里插入图片描述

单源最短路径

先来点涩图给xd助助兴??

Dijkstra算法

基于贪心思想,适合于非负权图

void dijkstra()
{
    memset(dis, 0x3f3f3f3f, sizeof(dis));
    dis[1] = 0;
    for (int i = 1; i < n; i++)
    {
        int x = 0;
        for (int j = 1; j <= n; j++)
        {
            if (!vis[j] && (x == 0 || dis[j] < dis[x]))
                x = j;
            vis[x] = 1;
            for (int j = 1; j <= n; j++)
                dis[j] = min(dis[j], dis[x] + a[x][j]);
        }
    }
}

堆优化的dijkstra算法

int dis[N], nex[N], to[N], val[N], head[N], ans[N];
bool vis[N];
inline void add(int u, int v, int w) //链式向前星
{
    to[++cnt] = v;      //终点
    val[cnt] = w;       //权值
    nex[cnt] = head[u]; //nex表示与这个边起点相同的上一条边的编号
    head[u] = cnt;
}
void dijkstra()
{
    priority_queue<pii, vector<pii>, greater<pii>> p; //定义小根堆
    memset(dis,0x3f3f3f3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[1] = 0;
    p.push(make_pair(0, 1)); //第一维是dis值,第二维是结点编号
    while (!p.empty())
    {
        int x = p.top().second; //x为dis最小的,标记结点
        p.pop();
        if (vis[x])
            continue;
        vis[x] = 1;
        for (int i = head[x]; i; i = nex[i]) //链式向前星的遍历,遍历以x为起点的边
        {
            int y = to[i], z = val[i];
            if (dis[y] > dis[x] + z) //松弛边
            {
                dis[y] = dis[x] + z;
                p.push(make_pair(dis[y], y));
                //   p.push(make_pair(-dis[to[i]], to[i])); //若为大根堆,可以利用相反数变小根堆
            }
        }
    }
}

Bellman-Ford算法

核心代码

for(k = 1; k <= n - 1; k++)
    for(i = 1; i <= m; i++)
        if(dis[v[i]] > dis[u[i]] + w[i])
            dis[v[i]] = dis[u[i]] + w[i];

SPFA算法

也叫队列优化的bellman-ford算法,主要是可以解决负权图的问题,一个结点可能入队、出队多次,目的是收敛到全部满足三角形不等式状态;队列优化是避免了bellman-Ford算法中对不需要扩展的结点的冗余扫描

queue<int>q;
void add(int u, int v, int w)
{
    to[++bcnt] = v;
    val[bcnt] = w;
    nex[bcnt] = head[u];
    head[u] = bcnt;
}
void spfa()
{
    memset(dis, 0x3f3f3f3f, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    dis[1] = 0;
    q.push(1);
    vis[1] = 1;
    while (q.size())
    {
        int x = q.front();
        q.pop();
        vis[x] = 0; //弹出来,相应地取消标记
        for (int i = head[x]; i; i = nex[i])
        {
            int y = to[i], z = val[i];
            if (dis[y] > dis[x] + z)
            {
                dis[y] = dis[x] + z;
                if (!vis[i])
                {
                    q.push(y);
                    vis[y] = 1; //推进去,相应的增加标记
                }
            }
        }
    }
}

牛客一个小栗子Telephone Lines

二分+双端队列spfaspfa与dijkstra的区别是spfa是不断走,不断试;dijkstra只贪心地走一次
这里处理数据有个小技巧,目的是求出1~n的最短路,双端队列适合处理边权只有0或1的最短路

#include <bits/stdc++.h>
using namespace std;
#pragma GCC optimize(3, "Ofast", "inline")
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 1e4 + 100;
const int inf = 0x3f3f3f3f;
const int mod = 1e9;
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
ll n, p, k, tot, ans;
ll head[N], nex[N << 1], to[N << 1], val[N << 1], dis[N];//数据小了卡到76
bool vis[N];
void add(ll u, ll v, ll w)
{
    to[++tot] = v;
    val[tot] = w;
    nex[tot] = head[u]; //cao小错也能犯giao
    head[u] = tot;
}
bool check(ll x)
{
    deque<ll> d;                   //双端队列bfs
    memset(dis, inf, sizeof(dis)); //注意其他点到1的距离初始为
    memset(vis, 0, sizeof(vis));
    dis[1] = 0;
    d.push_back(1);
    while (d.size())
    {
        int t = d.front();
        d.pop_front();
        if (vis[t]) //
            continue;
        vis[t] = 1;
        for (int i = head[t]; i; i = nex[i])
        {
            ll y = to[i], z = (val[i] > x ? 1 : 0);
            dis[y] = min(dis[y], dis[t] + z); //这里是t,不能习惯性的写x
            if (z)//优先扩展最短的,所以当z=0时放队头扩展,z=1时放队尾扩展
                d.push_back(y); //一次的转移并没有使得统计的边的条数变大,那么把它放在对头下一个还是由它去增广
            else
                d.push_front(y); //统计的值变大了,那么就放在队尾,这个值会在比他小的值作为起点都增广完毕之后轮到它增广
        }
    }
    return dis[n] <= k;
}
int main()
{
    // IOS;
    scanf("%lld%lld%lld", &n, &p, &k);
    for (int i = 1; i <= p; i++)
    {
        ll a, b, l;
        scanf("%lld%lld%lld", &a, &b, &l);
        add(a, b, l);
        add(b, a, l);
    }
    ll l = 0, r = inf, mid; //l初始为0
    while (l < r)
    {
        mid = (l + r) >> 1; //注意这种配对的二分不是mid=(l+r+1)>>1;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    if (l >= inf)
        ans = -1;
    else
        ans = l;
    printf("%lld\n", ans);
    return 0;
}

牛客一个小栗子Roads and Planes

#include <bits/stdc++.h>
using namespace std;
#pragma GCC optimize(3, "Ofast", "inline")
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 2e5 + 1000;//开大一点
const int inf = 0x3f3f3f3f;
const int mod = 1e9;
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
ll r, s, t, p, tot, ans, cnt;
ll head[N], nex[N], to[N], val[N], dis[N], deg[N], bel[N];
vector<ll> v[N];
bool vis[N];
void add(ll u, ll v, ll w)
{
    to[++tot] = v;
    val[tot] = w;
    nex[tot] = head[u];
    head[u] = tot;
}
void dfs(ll x) //查找连通块
{
    bel[x] = cnt;
    v[cnt].push_back(x);                 //加入自己的连通块队伍
    for (int i = head[x]; i; i = nex[i]) //访问与自己相关的结点
    {
        int y = to[i];
        if (!bel[y])
            dfs(y);
    }
}
void dijkstra()
{
    queue<ll> q;
    priority_queue<pll, vector<pll>, greater<pll>> p;
    memset(dis, inf, sizeof(dis));
    q.push(s); //
    dis[s] = 0;
    for (int i = 1; i <= cnt; i++) //
    {
        if (!deg[i])
            q.push(i);
    }
    while (q.size())
    {
        int x = q.front();
        q.pop();
        for (int i = 0; i < v[x].size(); i++) //访问自己的连通块,加入最小堆中
            p.push({dis[v[x][i]], v[x][i]});
        while (p.size()) //求每个连通块的最短路
        {
            ll x = p.top().second;
            p.pop();
            if (vis[x])
                continue;
            vis[x] = 1;
            for (int i = head[x]; i; i = nex[i])
            {
                ll y = to[i], z = val[i];
                if (dis[y] > dis[x] + z)
                {
                    dis[y] = dis[x] + z;
                }
                if (bel[x] == bel[y]) //仍在连通块内
                    p.push({dis[y], y});
                else
                {
                    deg[bel[y]]--;    //所属连通块
                    if (!deg[bel[y]]) //拓扑排序的操作
                        q.push(bel[y]);
                }
            }
        }
    }
}
int main()
{
    // IOS;
    scanf("%lld%lld%lld%lld", &t, &r, &p, &s);
    for (int i = 1; i <= r; i++)
    {
        ll x, y, z;
        scanf("%lld%lld%lld", &x, &y, &z);
        add(x, y, z);
        add(y, x, z);
    }
    for (int i = 1; i <= t; i++)
    {
        if (!bel[i]) //
            cnt++, dfs(i);
    }
    for (int i = 1; i <= p; i++)
    {
        ll x, y, z;
        scanf("%lld%lld%lld", &x, &y, &z);
        add(x, y, z);
        deg[bel[y]]++; //入度++
    }
    dijkstra();
    for (int i = 1; i <= t; i++) //题目让求的是到每个城镇最便宜的方案
    {
        if (dis[i] >= inf)
            puts("NO PATH");
        else
            printf("%lld\n", dis[i]);
    }
    return 0;
}

任意两点最短路径

Floyed算法

实质是动态规划的思想,dp[k,i,j]表示若干个编号不超过k的结点从i到j的最短路长度,k是阶段,i、j是状态,故k在最外面
状态转移方程为dp[k,i,j]=min(dp[k-1][i][j],dp[k-1][i][k]+dp[k-1][k][j])
同01背包一样,第一维k可以省略

int floyed()
{//将k看作中转站
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
}

传递闭包

使用floyed算法可以解决传递闭包的问题【dp[i][i]=1,1表示有关系,0表示没关系】

int floyed()
{//将k看作中转站
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                dp[i][j]|=dp[i][k]&dp[k][j];
}

牛客一个小栗子Sorting It All Out
拓扑排序做法

#include <bits/stdc++.h>
using namespace std;
#pragma GCC optimize(3, "Ofast", "inline")
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 2e5 + 1000; //开大一点
const int inf = 0x3f3f3f3f;
const int mod = 1e9;
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
ll n, m, tot, cnt;
ll deg[N], d[N];
char ans[N];
vector<ll> v[N];
ll topu()
{
    for (int i = 0; i < n; i++)
        d[i] = deg[i]; //
    queue<ll> q;
    for (int i = 0; i < n; i++)
        if (!d[i])
            q.push(i);
    ll flag = 0, cnt = 0; //
    while (q.size())
    {
        if (q.size() > 1) //不能装反,nmd
            flag = 1;
        ll x = q.front();
        q.pop();
        ans[cnt++] = x;
        for (int i = 0; i < v[x].size(); i++)
        {
            if (--d[v[x][i]] == 0) //
                q.push(v[x][i]);
        }
    }
    if (cnt != n)
        return -1; //
    else if (flag)
        return 0;
    else
        return 1;
}
int main()
{
    // IOS;
    while (scanf("%lld%lld", &n, &m) && n && m)
    {
        memset(deg, 0, sizeof(deg)); //
        for (int i = 0; i < n; i++)
            v[i].clear();
        char s[5];
        ll tmp, f = 0, id; //
        for (int i = 1; i <= m; i++)
        {
            scanf("%s", s);
            ll aa = s[0] - 'A', bb = s[2] - 'A';
            v[aa].push_back(bb);
            deg[bb]++;
            if (f)
                continue;
            tmp = topu();
            if (tmp == 1 || tmp == -1) //
                f = 1, id = i;
        }
        if (tmp == 1)
        {
            printf("Sorted sequence determined after %lld relations: ", id);
            for (int i = 0; i < n; i++) //
                printf("%c", ans[i] + 'A');
            puts(".");
        }
        else if (tmp == -1)
            printf("Inconsistency found after %lld relations.\n", id);
        else if (tmp == 0)
            printf("Sorted sequence cannot be determined.\n");
    }
    return 0;
}

Floyd传递闭包做法

#include <bits/stdc++.h>
using namespace std;
#pragma GCC optimize(3, "Ofast", "inline")
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 100;
const int inf = 0x3f3f3f3f;
const int mod = 1e9;
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
ll n, m;
ll dp[N][N], deg[N];
ll check()
{
    ll res = 1;
    for (int i = 1; i <= n; i++)
        for (int j = i + 1; j <= n; j++)
        {
            if (!dp[i][j] && !dp[j][i]) //自环
                res = -1;
            else if (dp[i][j] && dp[j][i]) //无序
                return 0;                  //不是res=0
        }
    return res;
}
int main()
{
    // IOS;
    while (scanf("%lld%lld", &n, &m) && n && m)
    {
        memset(dp, 0, sizeof(dp));   //
        for (int i = 1; i <= n; i++) //
            dp[i][i] = 1;
        char s[5];
        bool f = 0; //
        for (int id = 1; id <= m; id++)
        {
            scanf("%s", s);
            if (f)
                continue;
            ll a = s[0] - 'A' + 1, b = s[2] - 'A' + 1;
            dp[a][b] = 1;
            for (int i = 1; i <= n; i++)
                for (int j = 1; j <= n; j++)
                    dp[i][j] |= dp[i][a] & dp[b][j];
            ll tmp = check();
            if (tmp == 1)
            {
                printf("Sorted sequence determined after %lld relations: ", id);
                memset(deg, 0, sizeof(deg));
                for (int i = 1; i < n; i++) //
                    for (int j = i + 1; j <= n; j++)
                    {
                        if (dp[i][j])
                            deg[i]++;
                        else
                            deg[j]++;
                    }
                for (int i = n - 1; i >= 0; i--)
                    for (int j = 1; j <= n; j++)
                        if (deg[j] == i)//==不是=
                        {
                            // printf("%c", j + 'A' - 1);
                            putchar('A' - 1 + j);
                            break; //
                        }
                puts(".");
                f = 1;
                continue;
            }
            else if (tmp == 0)
            {
                printf("Inconsistency found after %lld relations.\n", id);
                f = 1;
                continue;
            }
        }
        if (!f)
        {
            puts("Sorted sequence cannot be determined.");
            continue;
        }
    }
    return 0;
}

无向图的最小环问题
牛客一道小栗子Sightseeing trip

#include <bits/stdc++.h>
using namespace std;
#pragma GCC optimize(3, "Ofast", "inline")
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 111;
const int inf = 0x3f3f3f3f;
const int mod = 1e9;
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
int n, m, tot, res = inf;
int path[N], pos[N][N], e[N][N], dp[N][N];
void init()
{
    memset(e, 0x3f, sizeof(e));
    for (int i = 1; i <= n; i++)
        e[i][i] = 0;
}
void getpath(ll a, ll b) //求从a到b的最短路经过的中间点
{
    if (pos[a][b] == 0)
        return;
    ll tmp = pos[a][b];
    getpath(a, tmp); //
    path[tot++] = tmp;
    getpath(tmp, b); //
}
void floyd()
{
    memcpy(dp, e, sizeof e); //
    for (int k = 1; k <= n; k++)
    {
        for (int i = 1; i < k; i++)
            for (int j = i + 1; j < k; j++) //i跟j不能重合
                if ((ll)e[i][k] + e[k][j] + dp[j][i] < res)
                {
                    res = e[i][k] + e[k][j] + dp[j][i];
                    tot = 0;
                    path[tot++] = k; //从k到i
                    path[tot++] = i;
                    getpath(i, j); //从i到j
                    path[tot++] = j;
                }
        for (int i = 1; i <= n; i++) //计算k-1号点前任意两点的最短路
            for (int j = 1; j <= n; j++)
                if (dp[i][j] > dp[i][k] + dp[k][j])
                {
                    dp[i][j] = dp[i][k] + dp[k][j];
                    pos[i][j] = k;
                }
    }
}
int main()
{
    // IOS;
    // scanf("%ld%ld", &n, &m);
    cin >> n >> m;
    init();
    for (int i = 1; i <= m; i++)
    {
        int u, v, w;
        // scanf("%ld%ld%ld", &u, &v, &w);
        cin >> u >> v >> w;
        e[u][v] = e[v][u] = min(e[u][v], w);
    }
    floyd();
    if (res == inf) //
        puts("No solution.");
    else
        for (int i = 0; i < tot; i++)
            printf("%lld ", path[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WTcrazy _

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值