• 「学习笔记」图论与网络流


    二分图

    匈牙利算法:不断找交替路径(这些边依次为没选、选、...、没选),然后把这些边状态取反,可以得到最大匹配

    下面是几个结论,适用于二分图,证明思路都是证大于等于和小于等于:

    1 证明:最大匹配 = 最小点覆盖

    首先,最大匹配 (leq) 最小点覆盖:一个最大匹配若有(m)条边,覆盖这些边至少要选(m)个点,因为最大匹配边没有公共顶点

    然后,最大匹配 (geq) 最小点覆盖:一个大小为(n)的点覆盖,我们依次加入每个点,加入时选一条没覆盖的邻边(一定能找到,否则这个点没用),就是说我们能得到一个大小为(n)的匹配。

    2 证明:点数 - 最小点覆盖 = 最大独立集

    点带权也是对的(不带权认为权是(1)),我们直接证带权的

    把最小点覆盖取个补集,发现是个独立集(如果补集出现相邻点,那么这条边一定没被覆盖),这个独立集权值<=最大独立集

    把最大独立集取个补集,发现是个点覆盖,这个点覆盖权值>=最小点覆盖

    3 证明:最大独立集 = 最小边覆盖

    qwq

    Tarjan相关

    割点:令(ch[u])表示(low[v] geq dfn[u])(v)个数,若(u)是祖先且(ch geq 1)(u)不是祖先且(ch geq 2),则(u)是割点

    void tarjan(int u, int fa) { //root : fa = 0
        dfn[u] = low[u] = ++ idx;
        int ch = 0;
        for(int i = 0; i < G[u].size(); i ++) {
            int v = G[u][i];
            if(!dfn[v]) {
                ch ++;
                tarjan(v, u);
                low[u] = min(low[u], low[v]);
                if(fa && low[v] >= dfn[u]) iscut[u] = 1;
            } else if(v != fa)
                low[u] = min(low[u], dfn[v]);
        }
        if(!fa && ch >= 2) iscut[u] = 1;
        ans += iscut[u];
    }
    

    :若(low[v] > dfn[u]),则边((u, v))是桥

    边双连通分量的一种求法是直接tarjan,不走刚刚过来的边。

    int dfn[N], low[N], idx, top, st[N], col[N], cnt, d[N];
    bool ins[N];
    void tarjan(int u, int pre = -404) {
    	dfn[u] = low[u] = ++ idx;
    	st[++ top] = u; ins[u] = 1;
    	for(int i = hd[u]; ~ i; i = e[i].nxt) {
    		int v = e[i].v;
    		if(!dfn[v]) {
    			tarjan(v, i ^ 1);
    			low[u] = min(low[u], low[v]);
    		} else if(i != pre && ins[v]) {
    			low[u] = min(low[u], dfn[v]);
    		}
    	}
    	if(low[u] == dfn[u]) {
    		int v; cnt ++;
    		do {
    			v = st[top --];
    			col[v] = cnt;
    			ins[v] = 0;
    		} while(v != u);
    	}
    }
    

    边双连通分量还有一种求法是删桥,然后dfs,这里不写了

    点双连通分量遇到树边和反向边都压到栈里,遇到一个割点就弹栈记录

    int dfn[N], low[N], idx, top, cnt, bel[N];
    edge st[N * N * 2];
    vector<int> bcc[N];
    
    void tarjan(int u, int fa = 0) {
    	dfn[u] = low[u] = ++ idx;
    	for(int v = 1; v <= n; v ++) if(G[u][v]) {
    		if(!dfn[v]) {
    			st[++ top] = (edge) {u, v};
    			tarjan(v, u);
    			low[u] = min(low[u], low[v]);
    			if(low[v] >= dfn[u]) {
    				cnt ++; bcc[cnt].clear();
    				while(1) {
    					edge e = st[top --];
    					if(bel[e.u] != cnt) {
    						bel[e.u] = cnt;
    						bcc[cnt].push_back(e.u);
    					}
    					if(bel[e.v] != cnt) {
    						bel[e.v] = cnt;
    						bcc[cnt].push_back(e.v);
    					}
    					if(e.u == u && e.v == v) break ;
    				}
    			}
    		} else if(dfn[v] < dfn[u] && v != fa) {
    			st[++ top] = (edge) {u, v};
    			low[u] = min(low[u], dfn[v]);
    		}
    	}
    }
    

    网络流

    Edmonds-Karp:暴力bfs,每次残量网络上找一条最短的s到t的路径增广。时间复杂度 (O(nm^2))

    Dinic:残量网络上利用bfs构造分层图,每次只考虑残量网络上层与层之间连的边进行dfs多路增广。时间复杂度上界 (O(n^2m)),实际上很快

    费用流(EK)算法算法中的bfs改为spfa,即每次找费用最小的路径增广,这样跑出来最大流的时候费用一定最小(贪心思想)

    最大流最小割定理https://blog.csdn.net/yjr3426619/article/details/82715779

    上下界网络流

    1 无源汇(循环流)的上下界可行流:((u, v, c, b))来描述一条弧,表示容量下界为(b),上界为(c)

    考虑我们先把每条弧流量设置为(b),得到一个初始流。这样得到的初始流不满足流量平衡(每个结点流入流量 = 流出流量),需要求出一个附加流,使初始流 + 附加流 = 满足流量平衡的可行流

    考虑结点(u):初始流中,流入 = 流出,则附加流中流入 = 流出;初始流中,流入比流出大/小 (k),则附加流中流出比流入大/小 (k),才能满足流量平衡

    也就是说对于结点(u),如果初始流流入 > 流出,附加流 流入 < 流出;如果初始流流出 > 流入,附加流 流出 < 流入

    可以得到一个做法:建立附加源(ss)和附加汇(tt),令(a[u] =) 流入 - 流出,若(a[u] > 0)(ss)(u)连一条容量为(a[u])的弧。若(a[u] < 0)(u)(tt)连一条容量为(-a[u])的弧。上面这两种弧称附加弧。

    根据每个点流量平衡,删去(ss)(tt)以后,附加流中每个点的流入和流出就不一定相等了,这也正是我们的目的。注意上述图中还有结点直接的弧,容量为(c - b),即附加流不能过大。

    我们希望所有附加弧都满流,这样才存在上述附加流。可以发现,跑(ss-tt)的最大流可以让附加弧尽可能满。如果最大流和所有与(ss)(或(tt))相邻的弧容量和相等,说明有解

    平面图最小割:每条边两侧的两个面连边,得到对偶图。平面图的一个割对应对偶图的一个环,并且环经过最外的面。最小环难以处理,可以(s)(t)连一条INF边,这样最小环转化为最短路。

    最大权闭合子图(s)向收益点连容量为收益大小的边,成本点向(t)连成本大小的边,总收益 - 最小割就是答案。割左边表示放弃收益,割右边表示付出成本。

  • 相关阅读:
    angularjs 学习理解
    setTimeout和setInterval
    c# 数组
    c# 枚举enum
    T_SQL 字符串函数
    T_SQL 日期函数
    T-SQL函数类型——系统函数
    SqlServer 删除日志
    ASP.NET中JSON的序列化和反序列化
    jquery 选择器
  • 原文地址:https://www.cnblogs.com/hongzy/p/11039925.html
Copyright © 2020-2023  润新知