阅读本文前,需具备以下知识:
二分图及染色法判定-CSDN博客
二分图最大匹配——匈牙利算法详解-CSDN博客
最大流—EK算法,流网络,残留网络,定理证明,详细代码-CSDN博客
最大流-Dinic算法,原理详解,四大优化,详细代码-CSDN博客
对于流网络中的最大流,不一定为整数,也可以是浮点数,所以我们有两个问题:
整数值可行流是否是二分图中一个匹配,二分图中的一个匹配是否对应一个整数值可行流?
对于可行流而言,由于所有边的容量上限都为1,所以每个左部点最多流经1点流量,也最多将这1点流量流向一个右部点,即每个左部点最多和一个右部点建立流量,换句话说,任取两条有流量的边,必然没有公共点,所以可行流是一个匹配。
那么对于一个匹配而言,我们匹配边赋予1点流量,再建立虚拟源点和虚拟汇点,我们发现新图除源汇点外满足容量守恒,斜对称和容量限制,所以是一个可行流。
于是建立了整数可行流和二分图最大匹配之间的双射关系。
为什么一定能找到一个整数值最大流?
由于建立的流网络中只用到了整数值,我们求解最大流的算法也都只用到了整数值,所以最大流一定是整数流
以P3386 【模板】二分图最大匹配 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)为模板
时间复杂度:(On m^0.5)
#include
#include
#include
#include
#include
using namespace std;
const int N = 1010, M = (50000 + N) << 1, inf = 1e9;
struct edge
{
int v, c, nxt;
} edges[M];
int n, m, e, s, t, head[N], d[N], cur[N], idx = 0;
inline void addedge(int u, int v, int c)
{
edges[idx] = {v, c, head[u]}, head[u] = idx++;
}
inline void add(int u, int v, int c)
{
addedge(u, v, c), addedge(v, u, 0);
}
int dfs(int u, int limit)
{
if (u == t)
return limit;
int res = 0;
for (int i = cur[u]; ~i && limit; i = edges[i].nxt)
{
cur[u] = i;
int v = edges[i].v;
if (d[v] == d[u] + 1 && edges[i].c)
{
int incf = dfs(v, min(limit, edges[i].c));
if (!incf)
d[v] = 0;
limit -= incf, res += incf, edges[i].c -= incf, edges[i ^ 1].c += incf;
}
}
return res;
}
bool bfs()
{
memset(d, 0, sizeof(d));
queue q;
q.emplace(s), d[s] = 1;
while (q.size())
{
int u = q.front();
q.pop();
for (int i = head[u]; ~i; i = edges[i].nxt)
{
int v = edges[i].v;
if (!d[v] && edges[i].c)
{
d[v] = d[u] + 1, q.emplace(v);
if (v == t)
return true;
}
}
}
return false;
}
int dinic()
{
int res = 0;
while (bfs())
memcpy(cur, head, sizeof(head)), res += dfs(s, inf);
return res;
}
int main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0), memset(head, -1, sizeof(head));
// freopen("in.txt", "r", stdin);
int a, b, c;
cin >> n >> m >> e, s = 0, t = n + m + 1;
for (int i = 0; i < e; i++)
cin >> a >> b, add(a, b + n, 1);
for (int i = 1; i <= n; i++)
add(s, i, 1);
for (int i = 1; i <= m; i++)
add(i + n, t, 1);
cout << dinic();
return 0;
}
P2756 飞行员配对方案问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
很显然是一个二分图最大匹配问题,建图跑板子即可,然后题目还要求我们输出匹配边的两个节点,我们遍历匹配边,反向边和正向边的邻接点即为两个匹配点
F1 Dinic
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
#include
F2 匈牙利求最大匹配
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
#include
P3254 圆桌问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
仍然是二分图最大匹配,不过这里是多重匹配,处理多重匹配我们的策略是拆点,当然可以用匈牙利来做,这里我们直接用Dinic,体会一下如何将问题抽象为网络流问题。
这里建图相较于匈牙利解法就很爽了,每个左部点右部点最大连边数就是它们跟源点汇点边的容量,然后左右部点之间互相连容量为1的边,然后跑板子即可。
#include
#include
#include
#include
#include
using namespace std;
const int N = 430, M = (150 * 270 + N) * 2;
const int inf = 1e9;
int n, m, s, t, idx = 0, cur[N], head[N], d[N];
struct edge
{
int v, c, nxt;
} edges[M];
inline void addedge(int u, int v, int c)
{
edges[idx] = {v, c, head[u]};
head[u] = idx++;
}
inline void add(int u, int v, int c)
{
addedge(u, v, c), addedge(v, u, 0);
}
bool bfs()
{
memset(d, 0, sizeof(d));
queue q;
q.emplace(s), d[s] = 1;
while (q.size())
{
int u = q.front();
q.pop();
for (int i = head[u]; ~i; i = edges[i].nxt)
{
int v = edges[i].v;
if (!d[v] && edges[i].c)
{
d[v] = d[u] + 1;
q.emplace(v);
if (v == t)
return true;
}
}
}
return false;
}
int dfs(int u, int limit)
{
if (u == t)
return limit;
int ret = 0;
for (int i = cur[u]; ~i && limit; i = edges[i].nxt)
{
cur[u] = i;
int v = edges[i].v;
if (d[v] == d[u] + 1 && edges[i].c)
{
int incf = dfs(v, min(limit, edges[i].c));
if (!incf)
d[v] = 0;
ret += incf, limit -= incf, edges[i].c -= incf, edges[i ^ 1].c += incf;
}
}
return ret;
}
int dinic()
{
int ret = 0;
while (bfs())
memcpy(cur, head, sizeof(head)), ret += dfs(s, inf);
return ret;
}
int main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
freopen("in.txt", "r", stdin);
cin >> m >> n;
s = 0, t = m + n + 1;
memset(head, -1, sizeof(head));
int tot = 0;
for (int i = 1; i <= m; i++)
{
int r;
cin >> r, add(s, i, r), tot += r;
}
for (int i = 1; i <= n; i++)
{
int c;
cin >> c, add(m + i, t, c);
}
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
add(i, m + j, 1);
if (dinic() != tot)
{
cout << '0';
}
else
{
cout << '1' << '\n';
for (int i = 1; i <= m; i++)
{
for (int j = head[i]; ~j; j = edges[j].nxt)
if (edges[j].v > m && edges[j].v <= m + n && !edges[j].c)
cout << edges[j].v - m << ' ';
cout << '\n';
}
}
}