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

二分图

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

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

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)连成本大小的边,总收益 - 最小割就是答案。割左边表示放弃收益,割右边表示付出成本。

原文地址:https://www.cnblogs.com/hongzy/p/11039925.html