zoukankan      html  css  js  c++  java
  • 图论总结

    这是一个图论总结。

    图的概念

    ·图:由一些点和一些边组成的东西
    ·有向图:边有方向(单向连通)的图
    ·无向图:边无方向(双向连通)的图
    ·入度:有多少边连入这个点
    ·出度:有多少边连出这个点
    ·DAG:有向无环图

    建图

    邻接矩阵

    用一个二维数组表示两个点之间有没有连边

    s[x][y] = dis; //x和y之间有一条长度为dis的边
    

    邻接表

    用一个结构体记录所有的边,同时保存以这条边为起点的上一条边的编号,然后保存所有起点的最后一条边即可。

    struct Edge{
        int next, to, dis;
    }e[MAXN * MAXN];
    int head[MAXN], num;
    inline void Add(int from, int to, int dis){
        e[++num].to = to;
        e[num].dis = dis;
        e[num].next = head[from];
        head[from] = num;
    }
    

    图的遍历

    ·DFS遍历
    ·BFS遍历

    拓扑排序

    ·定义:拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:
    1、每个顶点出现且只出现一次。
    2、若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

    ·求法:先把所有入度为(0)的点加入队列。当队列不为空时,取出队头并输出,删掉所有以该点为起点的边,并把所有新的入度为(0)的点加入队列。重复执行。

    queue <int> q;
    for(int i = 1; i <= n; ++i) 
       if(!in[i])  //in记录入度 
         q.push(i);
    while(!q.empty()){
      int now = q.front();
      q.pop();
      printf("%d ", now);
      for(int i=head[now];i;i=e[i].next)
         if(!(--in[e[i].to]))
           q.push(e[i].to);
    }
    

    最短路

    单源最短路

    · Dijkstra
    创建一个队列,保存“已经确定最短路”的点的集合。初始时队列里只有源点,除源点外所有点的距离赋值正无穷。
    当集合不为空时,取出集合内距离最短的一个点,移除集合,枚举这个点连的所有边,如果这个点的距离加上这条边的距离比到的那个点小,那么更新这条边到的那个点的距离,并把它加入集合。重复执行。使用堆优化后时间复杂度(O(nlog n))。适用于稠密图。

    //洛谷 P4779 【模板】单源最短路径(标准版)
    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <queue>
    using namespace std;
    inline int read(){
        int s = 0, w = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9'){if(ch == '-')w = -1;ch = getchar();}
        while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0',ch = getchar();
        return s * w;
    }
    const int MAXN = 100010;
    const int MAXM = 200010; 
    struct Node{
        int next, to, dis;
    }e[MAXM];
    int head[MAXN], num;
    ll dis[MAXN];
    bool v[MAXN];
    void Add(int from, int to, int dis){
        e[++num].to = to;
        e[num].next = head[from];
        e[num].dis = dis;
        head[from] = num;
    }
    int n, m, s;
    int a, b, c;
    struct cmp{
        bool operator () (int a, int b){
            return dis[a] > dis[b];
        }
    };
    priority_queue <int, vector<int>, cmp> q;
    int main(){
        n = read(); m = read(); s = read();
        for(i = 1; i <= m; ++i) a = read(), b = read(), c = read(), Add(a, b, c);
        for(i = 1; i <= n; ++i) dis[i] = INF;
        dis[s] = 0;
        q.push(s);
        while(!q.empty()){
          int now = q.top();
          q.pop();
          if(v[now]) continue;
          v[now] = true;
          for(int i = head[now];i;i = e[i].next){
             if(!v[e[i].to] && dis[e[i].to] > dis[now] + e[i].dis)
               dis[e[i].to] = dis[now] + e[i].dis, q.push(e[i].to);
          }
        }
        for(i = 1; i <= n; ++i) printf("%lld ", dis[i]);
        return 0;
    }
    

    · SPFA
    创建一个队列,初始时队列里只有源点。当队列不为空时,取出队头,和(Dijkstra)一样,枚举所有点,如果能更新距离,更新距离,如果更新的点不在队列里,则加入队列。重复执行。
    时间复杂度(O(ne)),其中(e)为平均每个点进队列的次数,一般图很小,适用于稀疏图,但遇到稠密图时间复杂度会退化为(O(nm))。还有就是不能跑有负环的图,因此也可以用来判断负环。

    //SPFA
    queue <int> q;
    q.push(s);   //s为源点 
    for(int i = 1; i <= n; ++i) dis[i] = INF;
    dis[s]=0;
    while(!q.empty()){
      int now = q.front();
      flag[now] = 0;
      q.pop();
      for(int i = head[now]; i; i = e[i].next){
         if(dis[e[i].to] > dis[now] + e[i].dis){
           dis[e[i].to] = dis[now] + e[i].dis;
           if(!flag[e[i].to])
             q.push(e[i].to), flag[e[i].to] = 1;
         }
      }
    }
    

    多源最短路

    ·定义:求任意两点之间的最短路
    ·Floyd
    初始时任意两点之间的距离为正无穷,任意点到自己的距离都为0,枚举所有“中介点”,再枚举所有点对,如果这两个点之间的距离大于起点到中介点再到终点的距离,则更新这两点之间的距离。时间复杂度(O(n^3))。此算法还可推广到求任意两点之间的连通性。

    //Floyd求多源最短路
    memset(dis, 127, sizeof dis);
    for(int i = 1; i <= n; ++i)
       dis[i][i] = 0;
    for(int k = 1; k <= n; ++k)
       for(int i = 1; i <= n; ++i)
          for(int j = 1; j <= n; ++j)
             dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
    

    Tarjan算法

    割点

    ·定义:若将该点及其所有的边删去后,图中剩余点的连通性发生变化,则该点为割点,一张图可能存在多个割点。
    ·求法(Tarjan):
    定义两个数组:(dfn)(时间戳),(low)(追溯值)
    先看一下代码:

    void Tarjan(int u,int f){    //vis[x]=1表示x为割点
        dfn[u]=low[u]=++id;
        int Max=0,son=0;
        for(int i=head[u];i;i=e[i].next){
           if(!dfn[e[i].to]){
             Tarjan(e[i].to,f);
             low[u]=min(low[u],low[e[i].to]);
             if(low[e[i].to]>=dfn[u]&&u!=f) vis[u]=1;
             if(u==f) ++son;
           }
           low[u]=min(low[u],dfn[e[i].to]);
        }
        if(u==f&&son>=2) vis[u]=1;
    }
    

    由代码可知,没访问到一个点就给其分配一个时间戳,(low)可理解为“该点能回到的时间戳最小的点的时间戳”。
    一个点(u)是割点,当且仅当该点不是根且存在一个有连边的点(v),满足

    [dfn[u]<=low[v] ]

    或者该点是根且存在两个有连边的点满足上述条件。
    根据定义,(dfn[u]<=low[v])时,说明(v)无法到达比(u)更早访问的点,于是把(x)删除后,(v)(u)的父亲之间无法连通。但如果(x)本来就是根,则无法影响到(v),所以需要存在两个点满足此条件。

    割点(桥)

    ·定义:若把该边删去后,图的连通性发生变化,则该边为桥。
    ·求法(Tarjan):
    设一条边连接(u,v)两个点,当(dfn[u]<low[v])时,该边为桥。
    和割点一样,(dfn[u]<low[v])说明(v)无法到达比(u)或比(u)更早的节点,删除该边后,(u)(v)“失联”,该边为桥。

    强联通分量

    ·定义:一个图中任意两点能互相到达的极大的子图。“极大”就是指若该子图满足条件,且不存在一个包含这个子图的图满足条件,则该子图就是”极大”。
    ·求法(Tarjan):
    Tarjan算法时把每个访问的点存到一个栈里,当对当前点的操作完成后判断,如果(dfn[u]==low[u]),则说明他的所有子节点无法到达比他更早的节点,那么这个点就是这个强联通分量的根,栈中所有在该点之上的点和该点共同构成一个强联通分量。
    ·缩点:把每个强联通分量看成一个点,重新连边即可。

    void Tarjan(int u){
        dfn[u] = low[u] = ++ID;
        s.push(u);
        vis[u] = 1;
        for(int i = head[u]; i; i = e[i].next){
           if(!dfn[e[i].to])
             Tarjan(e[i].to), low[u] = min(low[u], low[e[i].to]);
           else if(vis[e[i].to]) low[u] = min(low[u], dfn[e[i].to]);
        }
        if(dfn[u] == low[u]){
          ++sum;
          int now;
          do{
            now = s.top();
            s.pop();
            vis[now] = 0;  
            belong[now] = sum;   //属于哪个强联通分量
          }while(now != u);
        }
    }
    

    ·缩点的作用:把有环图变为无环图(树)

    ·定义:无向无环图
    ·链:树上两点之间的唯一路径

    树的直径

    ·定义:树上最长的链
    ·求法:从任意一个点(DFS),找到离这个点最远的点,再从那个最远的点(DFS),找到离那个点最远的点,这两个最远的点之间的路径就是树的直径。

    树的重心

    这里

    最小生成树

    ·定义:一个(n)个点图中选(n-1)条边使得这张图变成一棵树,且使这(n-1)条边的权值和最小。
    ·求法:

    Kruskal

    把所有边按权值升序排序,依次枚举所有边,如果这条边连接的两个点没有连通,则把这条边连上,计数器加上这条边的权值,当连了(n-1)条边后,算法结束。判断连通性用并茶集。算法时间复杂度(O(nlog n)),其实就是排序的复杂度。

    //洛谷 P3366 【模板】最小生成树
    #include <cstdio>
    #include <algorithm>
    using std::sort;
    const int MAXN = 5010;
    const int MAXM = 200010;
    int f[MAXN];
    int find(int x){
        return f[x] == x ? x : f[x] = find(f[x]);
    }
    void merge(int x, int y){
        f[find(y)] = find(x);
    }
    struct Edge{
        int x, y, z;
        bool operator < (const Edge A) const{
            return z < A.z;
        }
    }e[MAXM];
    int n, m, ans;
    int main(){
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++i) f[i] = i; --n;
        for(int i = 1; i <= m; ++i) scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].z);
        sort(e + 1, e + m + 1);
        for(int i = 1; i <= m && n; ++i)
           if(find(e[i].x) != find(e[i].y))
             merge(e[i].x, e[i].y), --n, ans += e[i].z;
        printf("%d
    ", ans);
        return 0;
    }
    

    次小生成树

    这里

    最近公共祖先(LCA)

    ·定义:距离树上两个节点最近的同时是两个节点的祖先的点。
    ·求法:

    倍增

    (f[u][i])表示节点(u)的第(2^i)个父亲, 用倍增转移状态。
    查询(LCA)的时候先跳到同一高度,然后跳到同一高度就行。
    具体看代码:

    int LCA(int u, int v){
        if(dep[u] > dep[v]) swap(u, v);
        int cha = dep[v] - dep[u];
        for(int i = 0; i <= LOGMAXN; ++i)
           if(cha & (1 << i))
             v = f[v][i];
        if(u == v) return u;
        for(int i = LOGMAXN; ~i; --i)
           if(f[u][i] != f[v][i])
             u = f[u][i], v = f[v][i];
        return f[u][0];
    }
    

    树链剖分(略)

    Tarjan离线求LCA

    点分治

    ·作用:快速枚举树上所有链
    ·方法:
    1、找树的重心 -> 2、从重心开始(DFS) -> 3、找所有经过当前点的链 -> 4、对所有子树进行1、2、3号操作,

    //洛谷 P2634 聪聪可可
    #include <cstdio>
    #include <algorithm>
    #define re register
    #define il inline
    using std::max;
    using std::sort;
    inline int read(){
        int s = 0, w = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); }
        while(ch >= '0' && ch <= '9') { s = s * 10 + ch - '0'; ch = getchar(); }
        return s * w;
    }
    const int MAXN = 20010;
    struct Edge{
        int next, to, dis;
    }e[MAXN << 1];
    int head[MAXN], num, n, size[MAXN], vis[MAXN], maxson[MAXN], Max, root, ans, s[5], p[5];
    inline void Add(int from, int to, int dis){
        e[++num].to = to;
        e[num].next = head[from];
        e[num].dis = dis;
        head[from] = num;
    }
    void getRoot(int u, int fa, int tot){
        maxson[u] = 0; size[u] = 1;
        for(re int i = head[u]; i; i = e[i].next){
           if(e[i].to != fa && !vis[e[i].to]){
             getRoot(e[i].to, u, tot);
             size[u] += size[e[i].to];
             maxson[u] = max(maxson[u], size[e[i].to]);
           }
        }
        maxson[u] = max(maxson[u], tot - size[u]);
        if(maxson[u] < Max) Max = maxson[u], root = u;
    }
    void getADB(int u, int fa, int dep){
        if(dep % 3) ans += s[3 - dep % 3];
        else ans += s[0] + 1;
        ++p[dep % 3];
        for(re int i = head[u]; i; i = e[i].next)
           if(e[i].to != fa && !vis[e[i].to])
             getADB(e[i].to, u, dep + e[i].dis);
    }
    void dfs(int u, int fa){
        vis[u] = 1;
        s[0] = s[1] = s[2] = p[0] = p[1] = p[2] = 0;
        for(re int i = head[u]; i; i = e[i].next){
           if(e[i].to != fa && !vis[e[i].to])
             getADB(e[i].to, u, e[i].dis), s[0] += p[0], s[1] += p[1], s[2] += p[2], p[0] = p[1] = p[2] = 0;
        }
        for(re int i = head[u]; i; i = e[i].next){
           if(!vis[e[i].to]){
             Max = n;
             getRoot(e[i].to, u, size[e[i].to]);
             dfs(root, u);
           }
        }
    }
    int A, B, C;
    int gcd(int a, int b){
        return !b ? a : gcd(b, a%b);
    }
    int main(){
        Max = n = read();
        for(re int i = 1; i < n; ++i){
           A = read(); B = read(); C = read();
           Add(A, B, C);
           Add(B, A, C);
        }
        getRoot(1, 0, n);
        dfs(root, 0);
        ans = (ans << 1) + n;
        int b = n * n;
        int GCD = gcd(ans, b);
        ans /= GCD;
        b /= GCD;
        printf("%d/%d
    ", ans, b);
        return 0;
    }
    

    二分图

    二分图的判定

    ·黑白染色法

    二分图匹配

    ·匈牙利算法

    网络流

  • 相关阅读:
    HDU 2433 Travel (最短路,BFS,变形)
    HDU 2544 最短路 (最短路,spfa)
    HDU 2063 过山车 (最大匹配,匈牙利算法)
    HDU 1150 Machine Schedule (最小覆盖,匈牙利算法)
    290 Word Pattern 单词模式
    289 Game of Life 生命的游戏
    287 Find the Duplicate Number 寻找重复数
    283 Move Zeroes 移动零
    282 Expression Add Operators 给表达式添加运算符
    279 Perfect Squares 完美平方数
  • 原文地址:https://www.cnblogs.com/Qihoo360/p/9614159.html
Copyright © 2011-2022 走看看