增广路思想的运用
构造二分图最小覆盖点方案
UVa11419 SAM I AM
求出最大匹配后,从所有X点集的未匹配点出发做dfs扩展匈牙利树,标记树中的所有点,则X中的未标记点和Y中的已标记点组成了所求的最小覆盖点集。
朴素的增广路算法也可有奇效
P4382 [八省联考 2018] 劈配
这题本质是二分图多重匹配,可以用网络流求解:按志愿等级优先的方式枚举选手当前志愿能否达成,可以用动态增删边的做法,但直接重新建图(先复制之前选手的网络流结果,再添加本次枚举需要的边)会更新高效,求解第二问用二分会更高效。
但是用朴素的增广路算法来解反而是最优的,并且不需要什么建模技巧。还是按志愿等级优先的方式枚举选手当前志愿能否达成,增广过程种保持之前选手的志愿达成等级不变即可,并且增广过程种记录一下使选手不沮丧的志愿中增广到的学员中编号最大的那个,即是第二问答案,连二分都不需要。
/**
* 洛谷 P4382 [八省联考 2018] 劈配
*/
#include <iostream>
#include <cstring>
using namespace std;
#define C 11
#define N 202
int w[N][N][C], c[N][N], py[N][N], px[N], rx[N], rk[N], b[N], t[N], s[N], m, n; bool vis[N];
bool match(int i, int j, int s) {
if (i < s) rk[s] = max(rk[s], i);
for (int k=0, d; k < c[i][j]; ++k) {
if (vis[d = w[i][j][k]]) continue;
vis[d] = true;
if (t[d] < b[d]) {
px[i] = d; rx[i] = j; py[d][t[d]++] = i;
return true;
}
for (int p=0; p < b[d]; ++p) if (match(py[d][p], rx[py[d][p]], s)) {
px[i] = j; rx[i] = j; py[d][p] = i;
return true;
}
}
return false;
}
void solve() {
cin >> n >> m; memset(c, 0, sizeof(c));
for (int i=1; i<=m; ++i) cin >> b[i], t[i] = 0;
for (int i=1; i<=n; ++i) {
px[i] = -1; rx[i] = m+1;
for (int j=1; j<=m; ++j) {
int a; cin >> a;
if (a) w[i][a][c[i][a]++] = j;
}
}
for (int i=1; i<=n; ++i) cin >> s[i];
for (int i=1; i<=n; ++i) {
memset(vis, rk[i] = 0, sizeof(vis));
for (int j=1; j<=m; ++j) if (match(i, j, j<=s[i] ? i : 0)) {
if (j <= s[i]) rk[i] = i;
break;
}
if (i > 1) cout << ' ';
cout << rx[i];
}
cout << endl;
for (int i=1; i<=n; ++i) {
if (i > 1) cout << ' ';
cout << i - rk[i];
}
cout << endl;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int t, _; cin >> t >> _;
while (t--) solve();
return 0;
}
一些可转化为二分图最大权匹配的好题
UVa1006/LA2238 Fixed Partition Memory Management
洛谷 P2053 [SCOI2007] 修车,洛谷上本题还有一个增强版(洛谷P2050 [NOI2012] 美食节),需要跑费用流的过程中动态加点连边才能过。
二分图无奇环性质的运用
UVa1364/LA3523 Knights of the Round Table
二分图最大匹配残量网络的强连通分量的应用
求出二分图的最大匹配后,对其残量网络跑Tarjan算法求出所有的SCC后,可得出原二分图上哪些边是最大匹配的必须边,哪些是可行边。
对残量网络求SCC的建图方法:新增源点s、汇点t;对X点集的匹配点
x
i
x_i
xi,连边
x
i
→
s
x_i\rightarrow s
xi→s,非匹配点
x
j
x_j
xj连边
s
→
x
j
s\rightarrow x_j
s→xj;对Y点集的匹配点
y
i
y_i
yi,连边
t
→
y
i
t\rightarrow y_i
t→yi,非匹配点
y
j
y_j
yj连边
y
j
→
t
y_j\rightarrow t
yj→t;对匹配边
(
x
i
,
y
j
)
(x_i,y_j)
(xi,yj)连反向边
y
j
→
x
i
y_j\rightarrow x_i
yj→xi,对非匹配边
(
x
i
,
y
j
)
(x_i,y_j)
(xi,yj)连正向边
x
i
→
y
j
x_i\rightarrow y_j
xi→yj。
结论:对匹配边
(
x
i
,
y
j
)
(x_i,y_j)
(xi,yj),当且仅当
x
i
x_i
xi与
y
j
y_j
yj在不同的SCC时是必须边;对原二分图的边
(
x
i
,
y
j
)
(x_i,y_j)
(xi,yj),当
x
i
x_i
xi与
y
j
y_j
yj在同一个SCC时是可行边。
求二分图最大匹配的可行边
UVa1327/LA2966 King’s Quest
本题由于存在完美匹配,求SCC建图其实不需要源点和汇点(去掉和源点汇点相连的边)。
求二分图最大匹配的必须边
P3731 [HAOI2017] 新型城市化
本题需要一通分析才知道是求二分图最大匹配的必须边,依据最大独立集=总点数-最小覆盖,最小覆盖=最大匹配。
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
#define N 10002
int cc[N], dx[N], dy[N], p[N], vis[N], q[N], clk, d, m, n, t; vector<int> g[N];
bool bipartite(int u) {
for (int i=g[u].size()-1, v; i>=0; --i) {
if (cc[v = g[u][i]] == cc[u]) return false;
if (!cc[v]) {
cc[v] = 3-cc[u];
if (!bipartite(v)) return false;
}
}
return true;
}
bool search() {
memset(dx, -1, sizeof(dx)); memset(dy, -1, sizeof(dy)); d = N;
int head = 0, tail = 0;
for (int i=1; i<=n; ++i) if (cc[i] == 1 && p[i] < 0) q[tail++] = i, dx[i] = 0;
while (head < tail) {
int u = q[head++];
if (dx[u] > d) break;
for (int i=g[u].size()-1, v; i>=0; --i) if (dy[v = g[u][i]] < 0) {
dy[v] = dx[u] + 1;
p[v] < 0 ? d = dy[v] : (dx[p[v]] = dy[v] + 1, q[tail++] = p[v]);
}
}
return d != N;
}
bool dfs(int u) {
for (int i=g[u].size()-1, v; i>=0; --i) if (vis[v = g[u][i]] != clk && dy[v] == dx[u]+1) {
vis[v] = clk;
if (p[v] >= 0 && dy[v] == d) continue;
if (p[v] < 0 || dfs(p[v])) {
p[u] = v; p[v] = u;
return true;
}
}
return false;
}
void max_match() {
memset(p, -1, sizeof(p)); memset(vis, -1, sizeof(vis));
for (clk=0; search(); ++clk) for (int i=1; i<=n; ++i) if (cc[i] == 1 && p[i] < 0) dfs(i);
}
int tarjan(int u) {
int low = dx[u] = ++clk; q[d++] = u;
for (int i=g[u].size()-1; i>=0; --i) {
int v = g[u][i];
if ((cc[u] == 1 && p[u] == v) || (cc[u] == 2 && v != t && p[u] != v)) continue;
if (!dx[v]) low = min(low, tarjan(v));
else if (!dy[v]) low = min(low, dx[v]);
}
if (low == dx[u]) {
++m;
while (true) {
dy[q[--d]] = m;
if (q[d] == u) break;
}
}
return low;
}
void solve() {
if (m == 0) {
cout << 0 << endl;
return;
}
t = n+1; memset(cc, 0, sizeof(cc));
for (int i=0; i<=t; ++i) g[i].clear();
while (m--) {
int u, v; cin >> u >> v; g[u].push_back(v); g[v].push_back(u);
}
for (int i=1; i<=n; ++i) if (!cc[i]) cc[i] = 1, bipartite(i);
max_match(); memset(dx, clk = 0, sizeof(dx)); memset(dy, m = d = 0, sizeof(dy));
for (int i=1; i<=n; ++i)
if (cc[i] == 1) p[i] > 0 ? g[i].push_back(0) : g[0].push_back(i);
else p[i] > 0 ? g[t].push_back(i) : g[i].push_back(t);
for (int i=0; i<=t; ++i) if (!dx[i]) tarjan(i);
d = 0;
for (int i=1; i<=n; ++i) if (p[i] > i && dy[i] != dy[p[i]]) ++d;
cout << d << endl;
for (int i=1; i<=n; ++i) if (p[i] > i && dy[i] != dy[p[i]]) cout << i << ' ' << p[i] << endl;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
while (cin >> n >> m) solve();
return 0;
}
求二分图字典序第k最大匹配方案
UVa1459/LA4748 Flowers Placement
求出一个最大匹配后,dfs枚举剪枝。