acm中的最短路基础

//写了几天图论最短路,做梦都是队列,邻接表啥的...

 

一些最短路算法:

 

Floyd:图灵奖大神弗洛伊德发明的算法(不是梦的解析那位弗洛伊德)。

代码最简单的最短路没有之一 3个循环一个赋值,注意中间点在最外层循环。

    for (k = 1; k <= n; k++)

        for (i = 1; i <= n; i++)

            for (j = 1; j <= n; j++)

                a[i][j] = min(a[i][j], a[i][k] + a[k][j]);

想法也很自然 如果从i先到k再到j比i到j近 就更新i到j的最小距离

Ijk都可以是任意点 所以复杂度O(n^3) 注意k在最外层就好

不要求无负边 而且不只是最短路,求闭包,负环都可以,很万能 只是时间复杂度O(n^3)。

 

Dijkstra:图灵奖大神Dijkstra发明的算法。

对于无负边的图,这通常是坠吼的求特定2点最短路的算法。

类似贪心的策略,简单说就是 开始只有起点在集合中,每次在其他边中找距集合任意点最近的边(所以可以用优先队列优化),把该边加入集合,并通过该点更新剩余点到集合的距离,直到终点加入集合。

直接复杂度O(n^2),优化后复杂度是O(nloge),acm中最实用的最短路算法。

关键在于每步的加点和更新条件,一些有限制的最短路就是通过修改条件实现的(比如第二短路)

 

 

 

Bellman-ford

可以有负边的单最短路算法 原始复杂度为O(ne),e为边数

和上一个算法基础思想类似,但它是通过边来更新最短路径。

因为e的最大值为n^2,可以当做它的最坏复杂度为O(n^3),太高。

所以通常用spfa算法,就是优化该算法后的结果,优化途径很多,平均效率大大提升,但其复杂度不定,稳定性不好。通常情况可以当做复杂度O(ke),k<2,据说最坏情况O(ne),题目中也会有换一种写法就ac不然就tle的情况。

 

上面2个算法网上的原理介绍,代码,优化都很多,贴一下个人模板。

最短路系列 都要链表存边(注意int溢出 初始化最大值足够大):

void add(int u, int v, int w)//初始k=1

{

    a[k].v = v, a[k].w = w, a[k].ne = h[u], h[u] = k++;

}

队列优化spfa(适合稀疏图 总体比dij慢):

int spfa(int str, int end)

{

    int i, u, v, w;

    for (i = 1; i <= n; i++) d[i] = 1 << 28;

    d[str] = 0;

    memset(vi, 0, sizeof(vi));

    queue<int>q;

    q.push(str);

    while (!q.empty())

    {

        u = q.front(), q.pop(), vi[u] = 0;

        for (i = h[u]; i; i = a[i].ne)

        {

            v = a[i].v, w = a[i].w;

            if (d[v] > d[u] + w)

            {

                d[v] = d[u] + w;

                if (!vi[v])

                    vi[v] = 1, q.push(v);

            }

        }

    }

    return d[end];

}

 

栈优化spfa(玄学速度 有时最快有时最慢):

int spfa(int sta,int end)

{

    int i, u, v, w, top = 0;

    for (i = 1; i <= n; i++)  d[i] = 1 << 28;

    d[sta] = 0, st[top++] = vi[sta] = 1;

    while (top)

    {

        u = st[--top], vi[u] = 0;

        for (i = h[u]; i; i = a[i].ne)

        {

            w = a[i].w, v = a[i].v;

            if (d[v] > d[u] + w)

            {

                d[v] = d[u] + w;

                if (!vi[v])

                    st[top++] = v, vi[v] = 1;

            }

        }

    }

    return d[end];

}

 

优先队列优化dij(适合稠密图 稳定速度 没见过卡这个tle的):

struct node

{

    int v, c;

    node(int _v = 0, int _c = 0) :v(_v), c(_c) {}

    bool operator <(const node &r)const

    {

        return c > r.c;

    }

};

int dij(int sta,int end)

{

    int i, v, u, w, top = 0;

    for (i = 1; i <= n; i++)

        vi[i] = 0, d[i] = 1 << 28;

    d[sta] = 0;

    priority_queue<node>q;

    q.push(node(sta, 0));

    node tmp;

    while (!q.empty())

    {

        tmp = q.top(), q.pop(), u = tmp.v;

        if (vi[u])continue;

        vi[u] = 1;

        for (i = h[u]; i; i = a[i].ne)

        {

            v = a[i].v, w = a[i].w;

            if (!vi[v] && d[v]>d[u] + w)

                d[v] = d[u] + w, q.push(node(v, d[v]));

        }

    }

    return d[end];

}

 

 

记忆化搜索

有向无环的没有负数的图,类似dp的跑一次就行,线性复杂度

Poj 3159 大规模数据最短路

#include<cstdio>

#define min(a,b) a>b?b:a

struct { int v, w, ne; }e[150005];

int k = 1, n, m, h[30005], d[30005], a, b, c, v[30005];

int dp(int u)

{

    if (v[u]) return d[u];

    v[u] = 1, d[u] = 1e10;

    for (int i = h[u]; i; i = e[i].ne)

        d[u] = min(d[u], dp(e[i].v) + e[i].w);

    return d[u];

}

int main()

{

    scanf("%d%d", &n, &m);

    while (m--&&scanf("%d%d%d", &a, &b, &c))

        e[k].v = a, e[k].w = c, e[k].ne = h[b], h[b] = k++;

    v[1] = 1;

    printf("%d\n", dp(n));

}

 

无向无环图(树)可以用lca(最近公共祖先求),离线的lca对于q组询问的复杂度是O(n + q),可以说是线性的了。

Lca最近公共祖先(tarjan离线算法 O(n+q)):

#include <bits/stdc++.h>

#define mm 40005

struct note

{

    int u, v, w, lca, ne;

} edge[mm << 1], edge1[805];

int head[mm], ip, head1[mm], ip1, m, n, fa[mm], vis[mm], ance[mm], dir[mm];

//head&edge存单向边 head1&edge1存每组询问 以1点为根第i点深度为dir[i]

void init()

{

    memset(vis, 0, sizeof(vis)), memset(dir, 0, sizeof(dir));

    memset(head, -1, sizeof(head)), memset(head1, -1, sizeof(head1));

    ip = ip1 = 0;

}

void add(int u, int v, int w)

{

    edge[ip].v = v, edge[ip].w = w, edge[ip].ne = head[u], head[u] = ip++;

}

void add1(int u, int v)

{

    edge1[ip1].u = u, edge1[ip1].v = v, edge1[ip1].lca = -1;

    edge1[ip1].ne = head1[u], head1[u] = ip1++;

}

int  find(int x)

{

    if (fa[x] == x)

        return x;

    return fa[x] = find(fa[x]);

}

void Union(int x, int y)

{

    x = find(x), y = find(y);

    if (x != y)

        fa[y] = x;

}

void tarjan(int u)

{

    int i, v, w;

    vis[u] = 1, ance[u] = fa[u] = u;

    for (i = head[u]; i != -1; i = edge[i].ne)

    {

        v = edge[i].v, w = edge[i].w;

        if (!vis[v])

            dir[v] = dir[u] + w, tarjan(v), Union(u, v);

    }

    for (i = head1[u]; i != -1; i = edge1[i].ne)

    {

        v = edge1[i].v;

        if (vis[v])

            edge1[i].lca = edge1[i ^ 1].lca = ance[find(v)];

    }

}

int main()

{

    //O(n+q)

    int u, v, w, i, lca;

    while (~scanf("%d%d", &n, &m))

    {

        init();

        for (i = 1; i < n; i++)

            scanf("%d%d%d", &u, &v, &w), add(u, v, w), add(v, u, w);

        for (i = 0; i < m; i++)

            scanf("%d%d", &u, &v), add1(u, v), add1(v, u);

        dir[1] = 0, tarjan(1);

        for (i = 0; i < m; i++)

        {

            u = edge1[i << 1].u, v = edge1[i << 1].v, lca = edge1[i << 1].lca;

            printf("%d\n", dir[u] + dir[v] - 2 * dir[lca]);

        }

    }

}

 

进阶还有A*算法之类的启发式搜索算法(现实中解决最短路问题很多也是A*算法),比赛中可以快速求第K短路,这就不介绍了。

 

 

 

求环的算法:拓扑排序可以O(n+e)判断图是否存在环,朴素bell可以O(n*e)的判断是否存在负环 spfa可以O(n*e)(时间常数小)的找到所有与负环连通的点(即不存在最短路的点,进队大于n次的点与负环连通) Floyd可以O(n^3)找到所有与负环连通的点(跑2次Floyd 前后答案不同的点即于负环相连)。

 

求某点的最小环:最简单的是floyd一遍。高效的一种算法是,spfa时把与s相连的所有点进队(s点不进),初始所有距离都是无穷包括起点(但队列里的点初始距离是它到起点的距离),然后正常跑spfa就好。

 

 

题目:

Poj 2387 裸O(n^2)求固定点间的最短路 注意重边

Poj 1847 阅读理解

Poj 2253&&poj 1797 最短路变形 推荐 不难

Poj 3268 最短路应用 O(n^2)才可过

Poj 1860&&poj3259&&poj2240 判断有向图是否存在负环 方法比较多

Poj 1502 阅读理解 输入比最短路难..(c语言基础 如果读取数字 但下一个输入不是数字 则改该数字读取失败 scanf返回值为0 数字值为0 输入的非数字仍然保留在输入区 直到下一个合法的读取)

Poj 3660 求闭包 数据小 可用floyd

Poj 1511 大数据规模的最短路

Poj 3159 差分约束 大数据规模的最短路

Poj 1062 墙裂推荐 比赛中大部分题目就是这样 非裸模板但需要图论知识的(好像dfs也能过)

LightOJ 1074 推荐 带负环的最短路

Hdu 4725 推荐 如何正确地建图+大数据最短路

Hdu 4370 神题...模型转换很难 转换为图论问题很容易少考虑 转换后思想简单 代码不太简单(如果数据范围小点 floyd能过就很棒了)

Poj3169 较复杂的差分约束

 

做完这些后应该能在20分钟写好加调试好朴素dij,spfa+队列/栈,优先队列优化的dij,并准备无误高效的模板。

 

比赛常用spfa和朴素dij,通常不会卡spfa的时间常数(也有不通常的时候,最好是正权值的边就用dij)。

难点是看出怎样应用在问题中...

 

通常最短路问题难在建图(建图不当的话在跑最短路甚至建图的时候就会t掉)或转化其他问题为图论,能高效地求最短路只是入门。

差分约束通常是最短路,关系,方阵之类也可以往图论考虑。

 

最后写一点图论wa的时候应该看的词:重边,环(负环),连通,零(负)边,inf值是否够大...

 

 

猜你喜欢

转载自blog.csdn.net/weixin_40191952/article/details/88983198