[总结] 图论部分总结

图论部分总结

1 普通图

1.1 最短路

代码实现如下:

* 求出每对点之间的距离 floyd O(n3) O ( n 3 )

for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);

* 单源最短路(适合稠密图) Dijkstra+heap O(nlog2n) O ( n l o g 2 n )

* 单元最短路(适合稀疏图)SPFA nlog2n n l o g 2 n

1.2 传递闭包

所谓传递性,可以这么理解:
对于三个点之间,若 a a 能到 b b , b b 能到 c c ,则 a a 能到 c c ,这便是传递性
传递闭包的计算过程一般可以用Warshell算法描述
代码实现如下:

for(int k=1;k<=n;k++)
  for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
      a[i][j]|=a[i][k]&&a[k][j];

1.3 最小生成树 (MST)

最小生成树一般有两种方法,Prim和Kruskal

* Prim

不断的找当前节点中连向其他节点的最小边,将该边以及其连接的节点加入当前节点中,直到没有其他节点为止

* Kruskal

从边集中找到最短的且其左右端点未联通的一条边加入边集中,直到加入 n1 n − 1 条边为止

1.4 k短路

BFS+A*

1.5 强连通分量

强连通分量是指在一个图中存在这样一个点的集合,他们之间互相可达,即为一个强联通分量

* Tarjan O(n+m) O ( n + m )

我们对于图进行深度优先搜索,将当前搜索路径上的点加入一个栈中,若对当前节点的处理完后在栈中依次弹出知道当前节点所能追溯到的最早节点
我们有两个数组dfn[]和low[]
dfn[x]为节点x搜索的次序
low[x]为节点x能追溯到的最早栈中节点搜索次序
代码实现如下:

void dfs(int x){
    low[x]=dfn[x]=++p;
    vis[x]=1;
    sta[++top]=x;//入栈 
    for(int i=head[x];i;i=edge[i].next){
        int to=edge[i].to;
        if(!dfn[to]){
            dfs(to);
            low[x]=min(low[x],low[to]);
        }
        else if(vis[to])
            low[x]=min(low[x],dfn[to]);
    }
    if(dfn[x]==low[x]){
        hash[x]=++tot;
        vis[x]=0;
        for(;sta[top]!=x;top--){//弹栈 
            hash[sta[top]]=tot;
            vis[sta[top]]=0;
        }
        top--;
    }
    return ;
}

* Kosaraju O(n+m) O ( n + m )

这个方法不常用,就不放了

1.6 双联通分量

不常用,也不会,待填坑
也可以用Tarjan算法

1.7 差分约束

不常用,也不会,待填坑

2 网络流

网络流(network-flows)是一种类比水流的解决问题方法,与线性规划密切相关。

2.1 最大流

在一个图中,有一个源点s与一个汇点t,图的边上有一个最大容量限制,问从S出发的流(flow)到达T的流量最多是多少

* 增广路算法(EK)

这个跑得挺慢的,不常用

* Dinic算法

这是一个畸形的Dinic模版(加了GAP优化而已啦,但是确实跑的挺快的)
代码实现如下:

int dfs(int now,int flow) {
    if(now==t)
        return flow;
    int use=0;
    for(int i=cur[now];i;i=e[i].next) {
        cur[now]=i;
        if(e[i].flow>0&&d[e[i].to]+1==d[now]) {
            int tmp=dfs(e[i].to,min(e[i].flow,flow-use));
            use+=tmp;
            e[i].flow-=tmp;
            e[i^1].flow+=tmp;
            if(flow==use)
                return use;
        }
    }
    cur[now]=head[now];

    if(!(--f[d[now]]))
        d[s]=m+2;//gap
    ++f[++d[now]];

    return use;
}
int main() {
    f[0]=m+2;
  int ans=0;
    while(d[s]2)
        ans+=dfs(s,1<<30);
}

2.2 最小割

将所有顶点分为两个集合S和T,其中s在S中,t在T中
将S和T相连的所有边删除就形成了两个互不可达的独立集合,称为一个割
每个割的容量定义为被删除的边的总容量,最小割即求容量最小的割

根据某些dalao的证明可得最小割即为最大流
emm哪些边是要割的其实我也不知道 (逃

2.3 二分图

二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。

2.3.1 二分图最大匹配

给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配。
在当前已完成的匹配下,无法再通过增加未完成匹配的边的方式来增加匹配的边数,即为二分图最大匹配

* 最大流

连边以后直接跑一个最大流就是最大匹配了

* 匈牙利算法

貌似挺快的,但是懒得学

2.3.2 二分图最小路径覆盖

(不可相交)
给定有向图G=(V,E)。设P是G的一个简单路(顶点不相交)的集合。如果V中每个顶点恰好在P的一条路上,则称P是G的一个路径覆盖。P中路径可以从V的任何一个顶点开始,长度也是任意的,特别地,可以为0。G的最小路径覆盖是G的所含路径条数最少的路径覆盖。

最小路径覆盖=总点数-最大匹配数
证明:
每增加一条边,路径减少一条,得证

(可相交)
未知

2.3.3 二分图最小点覆盖

对于图中的每条边至少有一个端点在覆盖集中,求最少点数

最小点覆盖=最大匹配数
就这样记住吧QAQ

2.3.4 二分图最大独立集

选出m个点,使其两两间没有边相连。

显然由二分图最小点覆盖可得
最大独立集=总点数-最大匹配数

2.3.5 二分图最小边覆盖

边覆盖是图的一个边子集,使该图上每一节点都与这个边子集中的一条边关联,

2.4 费用流

每次寻找可增广的费用最小(大)的路径进行增广
EK+SPFA
代码实现如下:

bool spfa() {
    memset(pre,-1,sizeof(pre));
    memset(dis,127/3,sizeof(dis));
    dis[s]=0;
    pd[s]=1;
    while(!q.empty()) q.pop();
    q.push(s);
    while(!q.empty()) {
        ll now=q.front();
        q.pop();
        pd[now]=0;
        for(int i=head[now];i;i=e[i].next) {
            if(dis[e[i].to]>dis[now]+e[i].cost && e[i].flow>0) {
                dis[e[i].to]=dis[now]+e[i].cost;
                pre[e[i].to]=now;
                pe[e[i].to]=i;
                if(!pd[e[i].to]) {
                    q.push(e[i].to);
                    pd[e[i].to]=1;
                }
            }
        }
    }
    return pre[t]!=-1;
}
void Mincost() {
    ll cost=0;
    ll flow=0;
    while(spfa()) {
        ll f=INF;
        for(int i=t;i!=s;i=pre[i])
            f=min(f,e[pe[i]].flow);
        flow+=f;
        for(int i=t;i!=s;i=pre[i]) {
            cost+=f*e[pe[i]].cost;
            e[pe[i]].flow-=f;
            e[pe[i]^1].flow+=f;
        }
    }
    printf("%lld",cost);
    return ;
}

2.5 有上下界最大流

(无源汇)
(有源汇)

3 拓扑排序

例子:
一个较大的工程往往被划分成许多子工程,我们把这些子工程称作活动。在整个工程中,有些子工程必须在其它有关子工程完成之后才能开始,也就是说,一个子工程的开始是以它的所有前序子工程的结束为先决条件的,但有些子工程没有先决条件,可以安排在任何时间开始。为了形象地反映出整个工程中各个子工程间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。
为了完成这个目标,也就是说在处理这个节点之前我们需要将其所有的儿子节点全部处理
那么那些节点没有先决条件(也就是没有子节点)?显然,出度为0的点是没有先决条件的.那么,只需维护一个出度为0的点的序列,将其处理后出队,并且将其父亲的出度-1,若某个节点的出度变为0,那么将其加入序列,直至序列为空,这个处理的顺序(或者说队列的顺序)就是拓扑序

emm,很简单吧

4 仙人掌图*

5 平面图*

你可能感兴趣的:(【图论】)