二分图
匈牙利算法:不断找交替路径(这些边依次为没选、选、...、没选),然后把这些边状态取反,可以得到最大匹配
下面是几个结论,适用于二分图,证明思路都是证大于等于和小于等于:
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)连成本大小的边,总收益 - 最小割就是答案。割左边表示放弃收益,割右边表示付出成本。