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

二分图

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

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

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

posted @ 2019-06-17 15:27  hfhongzy  阅读(521)  评论(0编辑  收藏  举报