ACM模板 图论

本文详细介绍了ACM竞赛中图论部分的重点知识,包括最短路算法(Dijkstra、Bellman-Ford、SPFA、Floyd等)、二分图最大匹配的匈牙利算法及其优化版本,以及最小生成树的Kruskal算法。同时,文章还提及了次短路、最长路、拓扑排序求最短路等相关问题,是ACM竞赛备赛的重要参考资料。
摘要由CSDN通过智能技术生成

@(ACM模板)[图论]

图论知识点要求

必须会:
- 次短路&&路径数
- 生成树(看里面的过程)
- 最大流(主要是模板)
- 割(论文:最小割模型在信息竞赛里的应用)
- 二分图
- 树的分治,(点分治如重心分解,边分治如树链剖分)

了解,会套模板即可:
- K短路
- 度限制MST~end
- ZKW数组模拟
- 一般图匹配(NP,套模板)
- 各种回路
- 了解经典问题

General

  1. 注意下标是0-indexed的还是1-indexed的
  2. 看好是否有重边、自环
  3. 多组数据,vector要clear
  4. 在无向图中,存边的数组的大小要开边数的两倍
  5. unidirectional 和 one-way都是单向边

建图

使用vector

链式前向星
  1. 设图中n个点,m条边。在稀疏图( mn )中表示图,可以用邻接表。每个结点i有一个链表,保存从i出发的所有边。
  2. 方法:用数组模拟链表:
    • 每条边编号
    • first[u]保存结点u的第一条边的编号
    • next[e]表示编号为e的边的下一条边的编号
    • 注意每次插到链表的首部而非尾部,避免遍历
  3. 代码实现
typedef long long LL;
const int maxn = ?;
const int maxm = ?;
int n, m;
int head[maxn], next[maxm];
struct Edge
{
    int to;
    int dis;//LL
    Edge(int to = 0, int dis = 0):to(to),dis(dis) {}
}edges[maxm];//无向图size为2*maxm
void build_graph()
{
    cin >>  n >> m;
    memset(head, 0xff, sizeof head);
    int u, v, w;
    for(int i = 0; i < m; ++i)
    {
        scanf("%d%d%d", &u, &v, &w);
        edges[i] = Edge(u, v, w);
        //无向图要加两条
        next[i] = head[u];
        head[u] = i;
    }
}

遍历u的所有边

for(int e = first[u]; ~e; e = next[e])
{
    //...
}

最短路

1. Dijkstra算法

思想&步骤:
1. 初始化:d[s] = 0, 其他d为INF, 确定点集里只有s
2. 从未被确定的点的集合里找d最小的,加入确定的点集,并更新其他d
3. 重复2直到所有点都确定

注意:
- 不能处理负权。原因:Dijkstra贪心的找未确定点集里d最小的,若有负权可能之后找到d更小的。
- 此为使用队列优化的版本,复杂度 O(mlog(n)) ,其中 n 为点数, m 为边数
- 基础版执行n次,每次遍历所有点,复杂度 O(n2)
基础版即使在 n2<mlog(n) 时也往往比队列版本慢,因为队列版本执行push操作的前提是能进行松弛操作,若这个式子不常常成立,则push操作会很少。

代码
注意:
- 若最短路是long long 的,下面的代码需要在相应注释的地方更改
- 若需记录路径,需要启用pa[]数组
- 注意点的下标是否从0开始

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5+5;//顶点数
const int maxm = 1e5+5;//边数

struct Dijstra//d[t] < inf代表有解
{
    const int inf = 0x3f3f3f3f;
    //const LL inf = 0x3f3f3f3f3f3f3f3f;

    int n, m;
    bitset<maxn> done;
    int d[maxn];//LL
    int head[maxn];
    //int par[maxn];//记录路径

    struct Edge
    {
        int to, nxt;
        int dis;//LL
    }e[maxm];

    struct Node
    {
        int u;//结点编号
        int dis;//LL
        bool operator<(const Node &rhs) const
        {
            return dis > rhs.dis;
        }
    };

    void init(int nn)
    {
        n = nn;
        m = 0;
        memset(head, 0xff, sizeof head);
    }

    void addEdge(int from, int to, int dis)//LL dis
    {
        e[m].to = to;
        e[m].dis = dis;
        e[m].nxt = head[from];
        head[from] = m++;
    }

    void finda(int s)
    {
        priority_queue<Node> q;
        for(int i = 0; i < n; ++i) d[i] = inf;//0~n-1,注意下标从哪里开始
        d[s] = 0;
        done.reset();
        q.push((Node){s, 0});

        while(!q.empty())
        {
            int u = q.top().u;
            q.pop();
            if(done[u] == true) continue;
            done[u] = true;
            for(int i = head[u]; ~i; i = e[i].nxt)
            {
                int v = e[i].to;
                int dis = e[i].dis;//LL
                if(d[v] > d[u] + dis)
                {
                    d[v] = d[u] + dis;
                    q.push((Node){v, d[v]});
                }
            }
        }
    }
};
2. Bellman-Ford算法

思想&步骤
1. 初始化 所有顶点 d[i] = INF, 令d[s] = 0
2. 枚举每条边进行松弛,不能松弛时算法结束
3. 重复2,使2进行n-1次

memset(d, 0x3f, sizeof d);
d[s] = 0;
for(int times = 1; times < n; ++times)
{
   for(int i = 0; i < m; ++i)
   {
       int x = u[i], y = v[i];
       if(d[y] < d[x] + w[i]) d[y] = d[x] + w[i];
   }
}
3 .SPFA算法

思想&步骤:
“松弛操作的连锁反应”
用queue存可用来松弛的点,每次用队首点进行松弛,然后将松弛到的&&不在queue中的点加入queue。

注意:
- 此为Bellman-Ford算法的队列优化版本
- 可以判断负环。因为任意一条最短路,所经过的点不会超过n,那么任何一点不可能超过(n-1)次被松弛。
- 最坏情况复杂度 O(nm) ,但实际中往往很快

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxm = 1e5 + 5;
const int maxn = 1e5 + 5;

struct SPFA
{
    const int inf = 0x3f3f3f3f;
    //const LL inf = 0x3f3f3f3f3f3f3f3f;

    bool vis[maxn];
    int c[maxn], head[maxn];//c为入队次数
    int d[maxn];//LL
    int n, m;

    struct Edge
    {
        int to, nxt;
        int dis;//LL
    }e[maxm];

    void init(int nn)
    {
        n = nn;
        m = 0;
        memset(head, 0xff, sizeof head);
    }

    void addEdge(int from, int to, int dis)//LL dis
    {
        e[m].to = to;
        e[m].dis = dis;
        e[m].nxt = head[from];
        head[from] = m++;
    }

    queue<int> q;
    bool finda(int s)//若存在负环返回true
    {
        memset(d, 0x3f, sizeof d);
        d[s] = 0;
        memset(vis, 0, sizeof vis);
        memset(c, 0, sizeof c);

        while(!q.empty()) q.pop();
        q.push(s);
        vis[s] = true;
        c[s] = 1;

        while(!q.empty())
        {
            int x = q.front();
            q.pop();
            vis[x] = false;
            for(int i = head[x]; ~i; i = e[i].nxt)
            {
                int y = e[i].to;
                int dis = e[i].dis;
                if(d[y] > d[x] + dis)
                {
                    d[y] = d[x] + dis;
                    if(!vis[y])
                    {
                        vis[y] = true;
                        ++
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值