一些有关图论的模板。

· · 算法·理论

最短路

dijkstra

Tip:无法处理负边权。

memset(d,0x3f,sizeof(d));
priority_queue< pair<int,int> > q;
q.push(make_pair(0,s));
d[s]=0; 
while(!q.empty){
    int u=q.top().second;
    q.pop();
    if(f[u]) continue;
    f[u]=1;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(d[v]>d[u]+e[i].w){
            d[v]=d[u]+e[i].w;
            if(!f[v]) q.push(make_pair(-d[v],v));
        }
    }
}

SPFA

memset(d,0x3f,sizeof(d));
d[s]=0,f[s]=1;
queue<int> q;
q.push(s);
while(!q.empty()){
    int u=q.front();
    q.pop(),f[u]=0;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(d[v]>d[u]+e[i].w){
            d[v]=d[u]+e[i].w;
            if(!f[v]){
                q.push(v);
                f[v]=1;
            }
        }
    }
}

判断负环

SPFA

memset(d,0x3f,sizeof(d));
d[s]=0,f[s]=1;
queue<int> q;
q.push(s);
while(!q.empty()){
    int u=q.front();
    q.pop(),f[u]=0;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(d[v]>d[u]+e[i].w){
            d[v]=d[u]+e[i].w;
            cnt[v]=cnt[u]+1;//统计前驱
            if(cnt[v]>=n){
                return false;//存在负环
            }
            if(!f[v]){
                q.push(v);
                f[v]=1;
            }
        }
    }
}

次短路

  1. 求最短路过程中顺带更新次短路。(可重复走边须 SPFA )
  2. 起点、终点各跑一次最短路,枚举各点。次短路即为某一条边长度 + 起点到一个端点最短 + 终点到另一端点最短。

    有关连通性

    Tarjan强连通分量

```cpp int cnt,scc,dfn[],low[],f[],col[]; stack<int> s; void Tarjan(int u){ dfn[u]=low[u]=++cnt; s.push(u),f[u]=1; for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(!dfn[v]){ Tarjan(v); low[u]=min(low[u],low[v]); }else if(f[v]){ low[u]=min(low[u],dfn[v]); } } if(low[u]==dfn[u]){ scc++; int x=-1; while(x!=u){ x=s.top(); s.pop(); f[x]=0; col[x]=u; } } } ``` ### 割点 1. $u$ 为树根,且子树数量 $>1$。 2. $u$ 不为树根,存在树枝边 $(u,v)$ 满足 $dfn_u\leq low_v$。 $tmp$ 统计 dfs 树中 $u$ 的树枝边数,$c$ 储存割点,$c_0$ 为割点数量。 ```cpp int cnt,dfn[],low[],root,c[]; void Tarjan(int u,int fr){ dfn[u]=low[u]=++cnt; int tmp=0; for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(!dfn[v]){ tmp++; Tarjan(v); low[u]=min(low[u],low[v]); if((u==root && tmp>1) || (u!=root && dfn[u]<=low[v])){ if(!c[u]) c[0]++; c[u]=1; } }else if(v!=fr){ low[u]=min(low[u],dfn[v]); } } } ``` #### 点双连通分量 1. Tarjan 找割点并把 dfs 遍历点入栈。 找到割点就弹栈直到弹出割点。弹出点即为该点双连通分量节点。割点重新入栈。 dfs 结束时将栈弹空。所弹出点也为一个点双连通分量。 3. 省略根节点判断,将割点判断条件直接改为 $dfn_u\leq low_v$。 同上进行入栈弹栈(发现孤立点或符合条件点)。 ### 割边(桥) 存在树枝边,满足 $dfn_u<low_v$。 ```cpp int cnt,dfn[],low[]; vector<int> b[]; void Tarjan(int u,int fr){ dfn[u]=low[u]=++cnt; for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(!dfn[v]){ tmp++; Tarjan(v); low[u]=min(low[u],low[v]); if(dfn[u]<low[v]){ if(u<v) b[u].push_back(v); else b[v].push_back(u); } }else if(v!=fr){ low[u]=min(low[u],dfn[v]); } } } ``` #### 边双连通分量 1. Tarjan 找桥,dfs 点入栈。 找到桥就弹栈,直至栈顶为 dfs 树父节点。dfs 结束后将栈弹空。 2. 将无向图看作有向图,禁止走同一无向边拆分出的回边。 寻找该有向图 scc。scc 即为边双连通分量。 ## 差分约束 n 元一次不等式组,形如 $x_i-x_j\le c_k$。均可变形为 $x_i\le x_j+c_k$,类似单源最短路转移式 $d_v\le d_u+w$,可将 $x_i$ 看作图中节点。 对于每个 $x_i-x_j\le c+k$,建立一条由 $j$ 到 $i$ 大小为 $c_k$ 的单向边。同时建立超级源点,与每个节点间有一条大小为 0 的边。 另:若 $x_i=x_j$ 则在 $i$ 、$j$ 间建立权值为 0 的双向边。 跑 SPFA,若存在负环则无解,否则 $x_i=d_i$ 即为一组合法解。