二分图匹配
-
二分图的判定:没有奇环
-
二分图的最大匹配
- 匈牙利算法
- 网络流
- 最大流
- 二分图最大匹配的必须边判定条件: ( x , y ) (x,y) (x,y) 流量为1,并且在残留网络上属于不同的强连通分量
- 二分图最大匹配的可行边判定条件: ( x , y ) (x,y) (x,y) 流量为1,并且在残留网络上属于同一个的强连通分量
- 最小割 (割的是边不是点、拆点建边)
- 最大流
-
二分图带权最大匹配
- KM
- 费用流
-
二分图的覆盖与独立集
- 最小点覆盖 = 最大匹配包含的边数
- 最大独立集 = 总点数 - 最大匹配边数
- 最小边覆盖 = 总点数 - 最大匹配边数
- 有向无环图的最小路径点覆盖 = 总点数 - 拆点后的最大匹配边数
二分图的最大匹配方案不一定唯一
那些年我用网络流做过的题型:
- 最小割最少边集
- 最大点覆盖
板子
匈牙利算法
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
const int INF = 0x3f3f3f3f;
int n, m, k;
namespace Match {//匈牙利算法 O(nm)
int set_a;//集合a的大小
int set_b;//集合b的大小
int mp[N][N];//建图
int matched[N];//是否已经匹配过
int vis[N];//本次访问 该点是否被访问过
bool found(int u) {//dfs找增光路
for (int i = 1; i <= set_b; ++i) {
if (mp[u][i]) {
if (vis[i])continue;
vis[i] = 1;
if (!matched[i] || found(matched[i])) {
matched[i] = u;
return true;
}
}
}
return false;
}
int match() {
int res = 0;
memset(matched, 0, sizeof(matched));
for (int i = 1; i <= set_a; ++i) {
memset(vis, 0, sizeof(vis));
if (found(i)) {
res++;
}
}
return res;
}
void init() {
set_a = n;
set_b = m;
}
}
using namespace Match;
int main() {
ios::sync_with_stdio(0);
cin >> n >> m >> k;
set_a = n;
set_b = m;
for (int i = 1, u, v; i <= k; ++i) {
cin >> u >> v;
if (u > n || v > m)continue;
mp[u][v] = 1;
}
cout << match() << endl;
return 0;
}
网络流
O ( m n ) O(m\sqrt{n}) O(mn)
// n 10^4~10^5
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f;
int n, m, k;
namespace Network_flows { //网络流板子
//设定起点和终点
int st;//起点-源点
int ed;//终点-汇点
struct egde {
int to, next;
int flow;//剩余流量
//int capacity;//容量
} e[N * 2];
int head[N], tot = 1;
void add(int u, int v, int w) {
e[++tot] = {v, head[u], w};
head[u] = tot;
e[++tot] = {u, head[v], 0};
head[v] = tot;//网络流反相边流量为0
}
int dep[N];//dep[]=-1时为炸点
queue<int> q;
bool bfs() {
memset(dep, 0, sizeof(dep));//顺便起到vis的功能
q.push(st);
dep[st] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (!dep[v] && e[i].flow) {
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
return dep[ed];
}
int dfs(int u, int Flow) {
if (u == ed) return Flow;
int now_flow = 0;//跑残流
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (dep[v] == dep[u] + 1 && e[i].flow) {
int f = dfs(v, min(Flow - now_flow, e[i].flow));
e[i].flow -= f;
e[i ^ 1].flow += f;
now_flow += f;
if (now_flow == Flow) return Flow;
}
}
if (now_flow == 0)dep[u] = -1;
return now_flow;
}
#define max_flow dinic
int dinic() {//最大流
int res = 0;
while (bfs()) {
res += dfs(st, INF);
}
return res;
}
void init() {
tot = 1;
memset(head, 0, sizeof(head));
while (!q.empty()) q.pop();
}
}
using namespace Network_flows;
int main() {
ios::sync_with_stdio(0);
cin >> n >> m >> k;
st = 0, ed = n + m + 1;
for (int i = 1, u, v; i <= k; ++i) {
cin >> u >> v;
if (u > n || v > m)continue;
add(u, v + n, INF);// u的编号1~n 为区分v点(1~m) 加上一个基准值
}
for (int i = 1; i <= n; ++i) {
add(st, i, 1);
}
for (int i = 1; i <= m; ++i) {
add(i + n, ed, 1);
}
cout << dinic() << endl;
return 0;
}
KM
namespace KM {// 带权最大匹配 KM板子 o(n^3)
// 前提条件:带权最大匹配一定是完备匹配(二分图左右均为n个点且含有n条匹配边)
int w[N][N];
int A[N], B[N];//左右部点的顶标
bool visA[N], visB[N];//访问标记:是否在交错树中
int match[N];//右部点匹配了哪一个左部点
int delta;
bool dfs(int x) {
visA[x] = 1;
for (int y = 1; y <= n; y++) {
if (!visB[y]) {
if (A[x] + B[y] - w[x][y] == 0) {//相等子图
visB[y] = 1;
if (!match[y] || dfs(match[y])) {
match[y] = x;
return true;
}
}
} else {
delta = min(delta, A[x] + B[y] - w[x][y]);
}
}
return false;
}
int KM() {
for (int i = 1; i <= n; i++) {
A[i] = -INF;
B[i] = 0;
for (int j = 1; j <= n; j++) {
A[i] = max(A[i], w[i][j]);
}
}
for (int i = 1; i <= n; i++) {
while (1) {
memset(A, 0, sizeof A);
memset(B, 0, sizeof B);
delta = INF;
if (dfs(i)) break;
for (int j = 1; j <= n; j++) {
if (A[j]) A[j] -= delta;
if (B[j]) B[j] += delta;
}
}
}
int res = 0;
for (int i = 1; i <= n; i++) {
res += w[match[i]][i];
}
return res;
}
}
using namespace KM;
费用流
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f;
int n, m, k;
namespace cost_flows { //最小费用最大流板子
//设定起点和终点
int st;//起点-源点
int ed;//终点-汇点
struct egde {
int to, next;
int flow;//剩余流量
int cost; //花费
} e[N * 2];
int head[N], tot = 1;
void add(int u, int v, int w, int c) {
e[++tot] = {v, head[u], w, c};
head[u] = tot;
e[++tot] = {u, head[v], 0, -c};
head[v] = tot;//网络流反相边流量为0 费用是-cost
}
queue<int> q;
int flow[N];//源点到此处的流量
int vis[N];//是否在队里里
int last[N];//每个点所连的前一条边
int cost[N];//最小花费
int pre[N];//每个节点的前驱节点
bool spfa() {
memset(flow, INF, sizeof(flow));
memset(cost, INF, sizeof(cost));//注意了 这里是最大值
memset(vis, 0, sizeof(vis));
q.push(st);
cost[st] = 0;
vis[st] = 1;
pre[ed] = -1;//ok
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (cost[v] > cost[u] + e[i].cost && e[i].flow) {// 最小费用
//if (cost[v] < cost[u] + e[i].cost && e[i].flow) { // 最大费用
cost[v] = cost[u] + e[i].cost;
pre[v] = u; // 前驱节点
last[v] = i; // u->v 对应的边
flow[v] = min(flow[u], e[i].flow);
if (!vis[v]) {
vis[v] = 1;
q.push(v);
}
}
}
}
return pre[ed] != -1;//即 没有到达汇点
}
int max_flow, min_cost;
void MCMF() {
while (spfa()) {
int now = ed;
max_flow += flow[ed];
min_cost += flow[ed] * cost[ed];
while (now != st) {
//从源点一直回溯到汇点
e[last[now]].flow -= flow[ed];
e[last[now] ^ 1].flow += flow[ed];
now = pre[now];
}
}
}
void init() {
tot = 1;
memset(head, 0, sizeof(head));
while (!q.empty()) q.pop();
max_flow=0;
min_cost=0;
}
}
using namespace cost_flows;
//最大流量只有一个 流法却有很多种 即要保持最大流又要保持最小费用 所以需要最短路
//由于dijkstra不能跑负权边 所以基本上用的都是spfa
//但是如果可以采用一种方法加上一个基准值base 将所有数据都跑在整数的话就可以了
int main() {
ios::sync_with_stdio(0);
cin >> n >> m >> st >> ed;
for (int i = 1,u,v,w,c; i <= m; ++i) {
cin>>u>>v>>w>>c;
add(u,v,w,c);
}
MCMF();
cout <<max_flow<<" "<<min_cost << endl;
return 0;
}
dijkstra版本
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int MAXN = 1e5;
const int INF = 0x7fffffff;
struct edge {
int to, capacity, cost, rev;
edge() {}
edge(int to, int _capacity, int _cost, int _rev) : to(to), capacity(_capacity), cost(_cost), rev(_rev) {}
};
struct Min_Cost_Max_Flow {
int V, H[MAXN + 5], dis[MAXN + 5], PreV[MAXN + 5], PreE[MAXN + 5];
vector<edge> G[MAXN + 5];
//调用前初始化
void Init(int n) {
V = n;
for (int i = 0; i <= V; ++i)G[i].clear();
}
//加边
void Add_Edge(int from, int to, int cap, int cost) {
G[from].push_back(edge(to, cap, cost, G[to].size()));
G[to].push_back(edge(from, 0, -cost, G[from].size() - 1));
}
//flow是自己传进去的变量,就是最后的最大流,返回的是最小费用
int Min_cost_max_flow(int s, int t, int f, int &flow) {
int res = 0;
fill(H, H + 1 + V, 0);
while (f) {
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
fill(dis, dis + 1 + V, INF);
dis[s] = 0;
q.push(pair<int, int>(0, s));
while (!q.empty()) {
pair<int, int> now = q.top();
q.pop();
int v = now.second;
if (dis[v] < now.first)continue;
for (int i = 0; i < G[v].size(); ++i) {
edge &e = G[v][i];
if (e.capacity > 0 && dis[e.to] > dis[v] + e.cost + H[v] - H[e.to]) {
dis[e.to] = dis[v] + e.cost + H[v] - H[e.to];
PreV[e.to] = v;
PreE[e.to] = i;
q.push(pair<int, int>(dis[e.to], e.to));
}
}
}
if (dis[t] == INF) break;
for (int i = 0; i <= V; ++i)H[i] += dis[i];
int d = f;
for (int v = t; v != s; v = PreV[v]) d = min(d, G[PreV[v]][PreE[v]].capacity);
f -= d;
flow += d;
res += d * H[t];
for (int v = t; v != s; v = PreV[v]) {
edge &e = G[PreV[v]][PreE[v]];
e.capacity -= d;
G[v][e.rev].capacity += d;
}
}
return res;
}
int Max_cost_max_flow(int s, int t, int f, int &flow) {
int res = 0;
fill(H, H + 1 + V, 0);
while (f) {
priority_queue<pair<int, int>> q;
fill(dis, dis + 1 + V, -INF);
dis[s] = 0;
q.push(pair<int, int>(0, s));
while (!q.empty()) {
pair<int, int> now = q.top();
q.pop();
int v = now.second;
if (dis[v] > now.first)continue;
for (int i = 0; i < G[v].size(); ++i) {
edge &e = G[v][i];
if (e.capacity > 0 && dis[e.to] < dis[v] + e.cost + H[v] - H[e.to]) {
dis[e.to] = dis[v] + e.cost + H[v] - H[e.to];
PreV[e.to] = v;
PreE[e.to] = i;
q.push(pair<int, int>(dis[e.to], e.to));
}
}
}
if (dis[t] == -INF)break;
for (int i = 0; i <= V; ++i)H[i] += dis[i];
int d = f;
for (int v = t; v != s; v = PreV[v])d = min(d, G[PreV[v]][PreE[v]].capacity);
f -= d;
flow += d;
res += d * H[t];
for (int v = t; v != s; v = PreV[v]) {
edge &e = G[PreV[v]][PreE[v]];
e.capacity -= d;
G[v][e.rev].capacity += d;
}
}
return res;
}
} MCMF;
int main() {
while (scanf("%d", &n)) {
// MCMF.Init(n); 初始化 邻接表
//MCMF.Add_Edge(begin, end, flow, cost);
}
return 0;
}