二分图学习笔记(施工中)
定义
对于一个无向图
一个二分图可以记作
判定
一个图是二分图
可以用 DFS 染色 或 扩展域并查集 判断。
例题
二分图最大匹配
定义
-
对于一个图
,如果一个边集 满足其中任意两条不同的边没有公共端点,则称 为图 的一个匹配。 -
对于匹配
,属于 的边叫做匹配边,不属于 的边叫做非匹配边。 -
如果点
是一条匹配边的端点,则称 为匹配点,否则是非匹配点。匹配边的两端都是匹配点;非匹配边的两端要么都是匹配点,要么一个是匹配点,一个是非匹配点。
-
对于二分图
,如果它的一个匹配 满足 ,则称 为 的一个完美匹配。
求解
二分图最大匹配问题可以用网络流求解。建立超级源点
在单位容量的网络中,使用 Dinic 的时间复杂度为
我太菜了,不会匈牙利算法。
点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define per(i, s, e) for(int i = s, i##E = e; i >= i##E; --i)
#define F first
#define S second
// #define int ll
#define gmin(x, y) (x = min(x, y))
#define gmax(x, y) (x = max(x, y))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double f128;
typedef pair<int, int> pii;
constexpr int N = 1005, M = 2e5 + 5;
int n, m, e, t;
int hd[N], to[M], nxt[M], cap[M], tot = 1;
int dep[N], cur[N];
void add(int u, int v, int w) {
to[++tot] = v;
cap[tot] = w;
nxt[tot] = hd[u];
hd[u] = tot;
}
bool bfs() {
memset(dep, 0, sizeof dep);
queue<int> q;
q.push(0);
dep[0] = 1;
while(!q.empty()) {
int u = q.front();
q.pop();
for(int i = hd[u]; i; i = nxt[i])
if(cap[i] && !dep[to[i]])
dep[to[i]] = dep[u] + 1, q.push(to[i]);
}
return dep[t];
}
int dfs(int u, int flow) {
if(u == t) return flow;
int res = 0;
for(int i = cur[u]; i && flow; i = nxt[i]) {
cur[u] = i;
if(dep[to[i]] == dep[u] + 1) {
int o = dfs(to[i], min(cap[i], flow));
flow -= o;
res += o;
cap[i] -= o;
cap[i ^ 1] += o;
}
}
return res;
}
int dinic() {
int ans = 0;
while(bfs()) {
rep(i, 0, t) cur[i] = hd[i];
ans += dfs(0, INT_MAX);
}
return ans;
}
signed main() {
#ifdef ONLINE_JUDGE
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> n >> m >> e;
t = n + m + 1;
rep(i, 1, e) {
int u, v; cin >> u >> v;
v += n;
add(u, v, 1), add(v, u, 0);
}
rep(i, 1, n) add(0, i, 1), add(i, 0, 0);
rep(i, 1, m) add(i + n, t, 1), add(t, i + n, 0);
cout << dinic() << endl;
return 0;
}
常见模型
最小路径覆盖
对于一个有向无环图
首先每个点单独构成一条路径肯定是一组路径覆盖。考虑通过图中的边合并这些路径使它变小。
将每个点
点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define per(i, s, e) for(int i = s, i##E = e; i >= i##E; --i)
#define F first
#define S second
// #define int ll
#define gmin(x, y) (x = min(x, y))
#define gmax(x, y) (x = max(x, y))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double f128;
typedef pair<int, int> pii;
constexpr int N = 205, M = 6005;
int n, m, t;
int to[M * 4], nxt[M * 4], cap[M * 4], hd[N * 2], tot = 1;
int cur[N * 2], dep[N * 2];
int fr[N];
void add(int u, int v, int w) {
to[++tot] = v;
cap[tot] = w;
nxt[tot] = hd[u];
hd[u] = tot;
}
bool bfs() {
memset(dep, 0, sizeof dep);
dep[0] = 1;
queue<int> q;
q.push(0);
while(!q.empty()) {
int u = q.front();
cur[u] = hd[u];
q.pop();
for(int i = hd[u]; i; i = nxt[i]) {
int v = to[i];
if(cap[i] && !dep[v])
dep[v] = dep[u] + 1, q.push(v);
}
}
return dep[t];
}
int dfs(int u, int flow) {
if(u == t) return flow;
int res = 0;
for(int i = cur[u]; i && flow; i = nxt[i]) {
cur[u] = i;
int v = to[i];
if(dep[v] == dep[u] + 1) {
int o = dfs(v, min(flow, cap[i]));
flow -= o;
res += o;
cap[i] -= o;
cap[i ^ 1] += o;
}
}
return res;
}
int dinic() {
int ans = 0;
while(bfs()) ans += dfs(0, INT_MAX);
return ans;
}
int find(int x) {
for(int i = hd[x]; i; i = nxt[i])
if(!(i & 1) && !cap[i]) return to[i] - n;
return 0;
}
signed main() {
#ifdef ONLINE_JUDGE
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> n >> m;
t = n * 2 + 1;
rep(i, 1, n) {
add(0, i, 1), add(i, 0, 0);
add(i + n, t, 1), add(t, i + n, 0);
}
rep(i, 1, m) {
int u, v; cin >> u >> v;
add(u, v + n, 1), add(v + n, u, 0);
}
int ans = n - dinic();
rep(i, 1, n) fr[find(i)] = i;
rep(i, 1, n) if(!fr[i]) {
cout << i << ' ';
int o = find(i);
while(o) {
cout << o << ' ';
o = find(o);
}
cout << endl;
}
cout << ans << endl;
return 0;
}
最小可重路径覆盖
对于一个有向无环图
做传递闭包后转化为不可重路径覆盖。
常用定理
König 定理
对于二分图
-
证明(含构造方案):
-
首先显然有最小点覆盖
,因为每条匹配边至少有一个端点被选择。 -
考虑从右部每个非匹配点开始 DFS,从右往左只能走非匹配边,从左往右只能走匹配边。设 左部被 DFS 到的点 和 右部没被 DFS 到的点 构成的集合为
, 就是一个最小点覆盖。 -
首先证明
:-
首先注意到每次 DFS 中,除了起点,被 DFS 到的都是匹配点。
-
因为从左往右只能走匹配边,也就只能走到匹配点。而从右往左虽然只能走非匹配边,但是走不到非匹配点,因为如果走到了非匹配点,就说明发生了以下这种情况:(红色为匹配边)
-
显然这存在更大的匹配,与最大匹配矛盾,因此不可能从右部走到非匹配点。
-
而右部的非匹配点都作为起点被 DFS 过,没被 DFS 过的只能是匹配点。如果右部的一个匹配点没被 DFS 过,它就会被加入
。右部的一个匹配点被 DFS 过,当且仅当其对应的左部匹配点也被 DFS 过,因此这个左部匹配点被加入 。这说明对于每条匹配边,都有且仅有一个端点被加入了 。因此 。
-
-
然后证明
是一个点覆盖:- 上文已经证明匹配边肯定全被覆盖。一条非匹配边没被
覆盖到的充要条件是,左部端点没被 DFS 过 且 右部端点被 DFS 过。 - 如果一个右部点被 DFS 过,则所有与它相连的非匹配边都被 DFS 过,因此这个条件不可能满足,即不存在未被覆盖的边,所以
是一个点覆盖。
- 上文已经证明匹配边肯定全被覆盖。一条非匹配边没被
-
综上,
是一个最小点覆盖。
-
推论:最大独立集
-
证明:
- 对于每个独立集
,其补集 都是一个点覆盖。 - 所以最大独立集对应最小点覆盖,大小为
。
- 对于每个独立集
最小边覆盖
对于二分图
-
证明:
- 一条边加入边覆盖中,可能覆盖了一个或两个未覆盖的点。
- 要使边覆盖最小,就要让覆盖了两个未覆盖点的边最多。
- 这种边就是匹配边,向边覆盖中加入每条匹配边,总共覆盖了
个点。 - 剩下的点每个点对应一条边,这些边的数量为
,边覆盖中的总边数就是 。
Hall 定理:
对于二分图
看着很对,证明不会。
例题:CF1009G Allowed Letters
有一个长为
的字符串 ,只包含 共 种字符。你知道每种字符的出现次数,和每个位置可能出现哪些字符,你需要构造出满足条件且字典序最小的 。
不难发现这是一个位置和字符之间的匹配问题,考虑建图网络流。
设字符
然而直接网络流难以找到字典序最小的匹配。网络流算法的流程很复杂,我们把握不住,所以尽量不要尝试改动网络流的板子,而是考虑其他方法。
最大 / 最小化字典序的问题都可以贪心。从前往后枚举位置
注意到如果不算源点和汇点,这就判断二分图是否有完美多重匹配(多重匹配:每个点
我们考虑使用 Hall 定理判定有没有完美匹配。
我们显然不能枚举位置部的所有子集,但是字符部只有
对于每个
设字符集为
点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define per(i, s, e) for(int i = s, i##E = e; i >= i##E; --i)
#define F first
#define S second
// #define int ll
#define gmin(x, y) (x = min(x, y))
#define gmax(x, y) (x = max(x, y))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double f128;
typedef pair<int, int> pii;
constexpr int N = 1e5 + 5;
int n, t, cnt[6], sum[64], f[N][64];
bool ava[N][6];
string s;
signed main() {
#ifdef ONLINE_JUDGE
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> s; n = s.size();
for(auto c : s) cnt[c - 'a']++;
rep(i, 0, 63) rep(j, 0, 5) if(i >> j & 1) sum[i] += cnt[j];
cin >> t;
memset(ava, 1, sizeof ava);
rep(i, 1, t) {
int p; cin >> p >> s;
memset(ava[p], 0, sizeof ava[p]);
for(auto c : s) ava[p][c - 'a'] = 1;
}
per(i, n, 1) {
memcpy(f[i], f[i + 1], sizeof f[i]);
rep(j, 0, 63) rep(k, 0, 5)
if((j >> k & 1) && ava[i][k])
{ ++f[i][j]; break; }
}
s.clear();
rep(i, 1, n) {
bool flg = 0;
rep(j, 0, 5) if(ava[i][j]) {
bool fl = 1;
rep(k, 0, 63)
if(k >> j & 1) {
if(f[i + 1][k] < sum[k] - 1) { fl = 0; break; }
}
else if(f[i + 1][k] < sum[k]) { fl = 0; break; }
if(fl) {
flg = 1; s.push_back(j + 'a');
rep(k, 0, 63) if(k >> j & 1) --sum[k];
break;
}
}
if(!flg) cout << "Impossible\n", exit(0);
}
cout << s << endl;
return 0;
}
Dilworth 定理
对于一个有向无环图
反链:如果一个点集
证明不会。
模板题:Luogu P4298 [CTSC2008] 祭祀
给定一个有向无环图
,有三问:
- 求出其最长反链长度。
- 构造最长反链。
- 判断每个点是否可能在最长反链中。
。
第一问,根据 Dilworth 定理,做出传递闭包,做最小不可重路径覆盖即可。构造的二分图
第二问,找出二分图的最大独立集
因为取出的每个
接下来证明这些点构成的反链是最长的:
设
第三问,枚举每个点分别判断。考虑强制选中这个点,然后再求最长反链,如果最长反链不变,这个点就可以在最长反链中。具体地,如果选中一个点,那么所有和这个点有偏序关系(路径)的点都不能选,所以将这些点都删掉,对剩下的点求最长反链,如果只减少了
时间复杂度:求传递闭包的时间复杂度为
点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define per(i, s, e) for(int i = s, i##E = e; i >= i##E; --i)
#define F first
#define S second
// #define int ll
#define gmin(x, y) (x = min(x, y))
#define gmax(x, y) (x = max(x, y))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double f128;
typedef pair<int, int> pii;
constexpr int N = 105, M = 1005;
int n, m, c[N][N];
int to[N * N], nxt[N * N], cap[N * N], hd[N * 2], tot = 1;
int cur[N * 2], dep[N * 2], t;
bool del[N * 2], vis[N * 2];
string s1, s2;
void add(int u, int v, int w) {
to[++tot] = v;
cap[tot] = w;
nxt[tot] = hd[u];
hd[u] = tot;
}
bool bfs() {
memset(dep, 0, sizeof dep);
dep[0] = 1;
queue<int> q;
q.emplace(0);
while(!q.empty()) {
int u = q.front();
q.pop();
cur[u] = hd[u];
for(int i = hd[u]; i; i = nxt[i]) {
int v = to[i];
if(cap[i] && !del[v] && !dep[v]) {
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
return dep[t];
}
int dfs(int u, int flow) {
if(u == t || !flow) return flow;
int res = 0;
for(int i = cur[u]; i && flow; i = nxt[i]) {
cur[u] = i;
int v = to[i];
if(!del[v] && dep[v] == dep[u] + 1) {
int o = dfs(v, min(cap[i], flow));
flow -= o;
res += o;
cap[i] -= o;
cap[i ^ 1] += o;
}
}
return res;
}
int dinic() {
int ans = 0;
while(bfs()) ans += dfs(0, n);
return ans;
}
void recover() {
rep(i, 2, tot)
if(i & 1) cap[i] = 0;
else cap[i] = 1;
memset(del, 0, sizeof del);
}
void dfs(int u) {
vis[u] = 1;
for(int i = hd[u]; i; i = nxt[i])
if(!vis[to[i]] && !cap[i]) dfs(to[i]);
}
signed main() {
#ifdef ONLINE_JUDGE
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> n >> m;
rep(i, 1, m) {
int u, v; cin >> u >> v;
c[u][v] = 1;
}
rep(k, 1, n) rep(i, 1, n) rep(j, 1, n)
c[i][j] |= c[i][k] & c[k][j];
t = n * 2 + 1;
rep(i, 1, n) rep(j, 1, n)
if(c[i][j]) add(i, j + n, 1), add(j + n, i, 0);
rep(i, 1, n)
add(0, i, 1), add(i, 0, 0), add(i + n, t, 1), add(t, i + n, 0);
int l = n - dinic();
cout << l << endl;
vis[0] = vis[t] = 1;
for(int i = hd[t]; i; i = nxt[i])
if(!cap[i]) dfs(to[i]);
rep(i, 1, n) s1.push_back(!vis[i] && vis[i + n] ? '1' : '0');
cout << s1 << endl;
rep(i, 1, n) {
recover();
del[i] = del[i + n] = 1;
int t = n - 1;
rep(j, 1, n) if(c[i][j] | c[j][i]) del[j] = del[j + n] = 1, --t;
s2.push_back(t - dinic() == l - 1 ? '1' : '0');
}
cout << s2 << endl;
return 0;
}
练习题
1. Luogu P2756 飞行员配对方案问题
二分图最大匹配板子。
点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define per(i, s, e) for(int i = s, i##E = e; i >= i##E; --i)
#define F first
#define S second
// #define int ll
#define gmin(x, y) (x = min(x, y))
#define gmax(x, y) (x = max(x, y))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double f128;
typedef pair<int, int> pii;
constexpr int N = 105, M = 2e4 + 5;
int n, m, t;
int to[M], nxt[M], cap[M], hd[N], tot = 1;
int dep[N], cur[N];
void add(int u, int v, int w) {
to[++tot] = v;
cap[tot] = w;
nxt[tot] = hd[u];
hd[u] = tot;
}
bool bfs() {
memset(dep, 0, sizeof dep);
dep[0] = 1;
queue<int> q;
q.push(0);
cur[0] = hd[0];
while(!q.empty()) {
int u = q.front();
q.pop();
for(int i = hd[u]; i; i = nxt[i]) {
int v = to[i];
if(!dep[v] && cap[i])
cur[v] = hd[v], dep[v] = dep[u] + 1, q.push(v);
}
}
return dep[t];
}
int dfs(int u, int flow) {
if(u == t) return flow;
int res = 0;
for(int i = cur[u]; i && flow; i = nxt[i]) {
int v = to[i];
cur[u] = i;
if(dep[v] == dep[u] + 1) {
int o = dfs(v, min(cap[i], flow));
res += o;
flow -= o;
cap[i] -= o;
cap[i ^ 1] += o;
}
}
return res;
}
int dinic(int ans = 0) {
while(bfs()) ans += dfs(0, INT_MAX);
return ans;
}
signed main() {
#ifdef ONLINE_JUDGE
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> m >> n;
t = n + 1;
int u, v; cin >> u >> v;
while(u != -1) {
add(u, v, 1);
add(v, u, 0);
cin >> u >> v;
}
rep(i, 1, m) add(0, i, 1), add(i, 0, 0);
rep(i, m + 1, n) add(i, t, 1), add(t, i, 0);
cout << dinic() << endl;
rep(u, 1, m) {
for(int i = hd[u]; i; i = nxt[i])
if(!(i & 1) && !cap[i]) cout << u << ' ' << to[i] << endl;
}
return 0;
}
2. Luogu P2763 试题库问题
二分图最大多重匹配板子。
点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define per(i, s, e) for(int i = s, i##E = e; i >= i##E; --i)
#define F first
#define S second
// #define int ll
#define gmin(x, y) (x = min(x, y))
#define gmax(x, y) (x = max(x, y))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double f128;
typedef pair<int, int> pii;
constexpr int N = 1005, K = 25;
int n, k, T;
int to[N * K * 2], nxt[N * K * 2], cap[N * K * 2], hd[N + K], tot = 1;
int dep[N + K], cur[N + K];
void add(int u, int v, int w) {
to[++tot] = v;
cap[tot] = w;
nxt[tot] = hd[u];
hd[u] = tot;
}
bool bfs() {
memset(dep, 0, sizeof dep);
dep[0] = 1;
queue<int> q;
q.push(0);
while(!q.empty()) {
int u = q.front(); q.pop();
cur[u] = hd[u];
for(int i = hd[u]; i; i = nxt[i]) {
int v = to[i];
if(!dep[v] && cap[i])
dep[v] = dep[u] + 1, q.push(v);
}
}
return dep[T];
}
int dfs(int u, int flow) {
if(u == T || !flow) return flow;
int res = 0;
for(int i = cur[u]; i && flow; i = nxt[i]) {
cur[u] = i;
int v = to[i];
if(dep[v] == dep[u] + 1) {
int o = dfs(v, min(cap[i], flow));
flow -= o, res += o;
cap[i] -= o, cap[i ^ 1] += o;
}
}
return res;
}
void dinic() {
while(bfs()) dfs(0, INT_MAX);
}
signed main() {
#ifdef ONLINE_JUDGE
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> k >> n;
T = n + k + 1;
rep(i, 1, k) {
int x; cin >> x;
add(0, i, x), add(i, 0, 0);
}
rep(i, k + 1, k + n) {
int c; cin >> c;
while(c--) {
int x; cin >> x;
add(x, i, 1), add(i, x, 0);
}
add(i, T, 1), add(T, i, 0);
}
dinic();
rep(i, 1, k) {
cout << i << ':';
for(int j = hd[i]; j; j = nxt[j]) {
int v = to[j];
if(v && !cap[j]) cout << ' ' << v - k;
}
cout << endl;
}
return 0;
}
3. Luogu P3254 圆桌问题
也是二分图最大多重匹配板子。
点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define per(i, s, e) for(int i = s, i##E = e; i >= i##E; --i)
#define F first
#define S second
// #define int ll
#define gmin(x, y) (x = min(x, y))
#define gmax(x, y) (x = max(x, y))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double f128;
typedef pair<int, int> pii;
constexpr int M = 155, N = 275;
int m, n, T, sum;
int hd[N + M], to[N * M * 2], nxt[N * M * 2], cap[N * M * 2], tot = 1;
int dep[N + M], cur[N + M];
void add(int u, int v, int w) {
to[++tot] = v;
cap[tot] = w;
nxt[tot] = hd[u];
hd[u] = tot;
}
bool bfs() {
memset(dep, 0, sizeof dep);
queue<int> q;
dep[0] = 1;
q.push(0);
while(!q.empty()) {
int u = q.front();
q.pop();
cur[u] = hd[u];
for(int i = hd[u]; i; i = nxt[i])
if(cap[i] && !dep[to[i]])
dep[to[i]] = dep[u] + 1, q.push(to[i]);
}
return dep[T];
}
int dfs(int u, int flow) {
if(u == T || !flow) return flow;
int res = 0;
for(int i = cur[u]; i && flow; i = nxt[i]) {
cur[u] = i;
int v = to[i];
if(dep[v] == dep[u] + 1) {
int o = dfs(v, min(flow, cap[i]));
flow -= o, res += o;
cap[i] -= o, cap[i ^ 1] += o;
}
}
return res;
}
int dinic() {
int ans = 0;
while(bfs()) ans += dfs(0, INT_MAX);
return ans;
}
signed main() {
#ifdef ONLINE_JUDGE
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> m >> n;
T = m + n + 1;
rep(i, 1, m) {
int r; cin >> r; sum += r;
add(0, i, r), add(i, 0, 0);
rep(j, m + 1, m + n)
add(i, j, 1), add(j, i, 0);
}
rep(i, m + 1, m + n) {
int c; cin >> c;
add(i, T, c), add(T, i, 0);
}
if(dinic() == sum) cout << "1\n";
else cout << "0\n", exit(0);
rep(i, 1, m) {
for(int j = hd[i]; j; j = nxt[j])
if(to[j] && !cap[j]) cout << to[j] - m << ' ';
cout << endl;
}
return 0;
}
4. Luogu P2765 魔术球问题
构造图
从
点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define per(i, s, e) for(int i = s, i##E = e; i >= i##E; --i)
#define F first
#define S second
// #define int ll
#define gmin(x, y) (x = min(x, y))
#define gmax(x, y) (x = max(x, y))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double f128;
typedef pair<int, int> pii;
constexpr int N = 60;
int n, T;
int hd[N * N * 2], to[N * N * 16], nxt[N * N * 16], cap[N * N * 16], tot = 1;
int dep[N * N * 2], cur[N * N * 2];
bool vis[N * N];
#define ou(x) ((x) << 1)
#define in(x) ((x) << 1 | 1)
inline bool chk(int x) {
int o = sqrtl(x);
if(o * o == x) return 1;
return 0;
}
inline void add(int u, int v, int w) {
to[++tot] = v;
cap[tot] = w;
nxt[tot] = hd[u];
hd[u] = tot;
}
bool bfs() {
memset(dep, 0, sizeof dep);
dep[1] = 1;
queue<int> q;
q.push(1);
while(!q.empty()) {
int u = q.front();
q.pop();
cur[u] = hd[u];
for(int i = hd[u]; i; i = nxt[i]) {
int v = to[i];
if(!dep[v] && cap[i])
dep[v] = dep[u] + 1, q.push(v);
}
}
return dep[T];
}
int dfs(int u, int flow) {
if(u == T || !flow) return flow;
int res = 0;
for(int i = cur[u]; i && flow; i = nxt[i]) {
cur[u] = i;
int v = to[i];
if(dep[v] == dep[u] + 1) {
int o = dfs(v, min(cap[i], flow));
flow -= o, res += o;
cap[i] -= o, cap[i ^ 1] += o;
}
}
return res;
}
int find(int u) {
for(int i = hd[ou(u)]; i; i = nxt[i])
if(to[i] > 1 && !cap[i]) return to[i] >> 1;
return 0;
}
signed main() {
#ifdef ONLINE_JUDGE
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> n;
int i = 1, ans = 0;
T = n * n * 2;
while(1) {
add(1, ou(i), 1), add(ou(i), 1, 0);
add(in(i), T, 1), add(T, in(i), 0);
rep(j, 1, i - 1) if(chk(i + j))
add(ou(j), in(i), 1), add(in(i), ou(j), 0);
while(bfs()) ans += dfs(1, INT_MAX);
if(i - ans > n) break;
++i;
}
--i;
cout << i << endl;
vis[0] = 1;
rep(j, 1, i) if(!vis[j]) {
int o = j;
do {
vis[o] = 1;
cout << o << ' ';
o = find(o);
} while(!vis[o]);
cout << endl;
}
return 0;
}
5. Luogu P2172 [国家集训队] 部落战争
相对上一题更容易想到,每个点向它能到达的点连边,即可转化为最小路径覆盖问题。
点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define per(i, s, e) for(int i = s, i##E = e; i >= i##E; --i)
#define F first
#define S second
// #define int ll
#define gmin(x, y) (x = min(x, y))
#define gmax(x, y) (x = max(x, y))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double f128;
typedef pair<int, int> pii;
constexpr int N = 55;
int n, m, r, c, T, cnt;
char s[N][N];
int hd[N * N * 2], to[N * N * 16], nxt[N * N * 16], cap[N * N * 16], tot = 1;
int dep[N * N * 2], cur[N * N * 2];
inline int id(int x, int y) {
return (x - 1) * m + y;
}
inline int ou(int x) {
return x << 1;
}
inline int in(int x) {
return x << 1 | 1;
}
void add(int u, int v, int w) {
to[++tot] = v;
nxt[tot] = hd[u];
cap[tot] = w;
hd[u] = tot;
}
bool bfs() {
memset(dep, 0, sizeof dep);
dep[0] = 1;
queue<int> q;
q.push(0);
while(!q.empty()) {
int u = q.front();
q.pop();
cur[u] = hd[u];
for(int i = hd[u]; i; i = nxt[i]) {
int v = to[i];
if(cap[i] && !dep[v])
dep[v] = dep[u] + 1, q.push(v);
}
}
return dep[T];
}
int dfs(int u, int flow) {
if(u == T || !flow) return flow;
int res = 0;
for(int i = cur[u]; i; i = nxt[i]) {
cur[u] = i;
int v = to[i];
if(dep[v] == dep[u] + 1) {
int o = dfs(v, min(cap[i], flow));
flow -= o, res += o;
cap[i] -= o, cap[i ^ 1] += o;
}
}
return res;
}
int dinic() {
int ans = 0;
while(bfs()) ans += dfs(0, INT_MAX);
return ans;
}
signed main() {
cin >> n >> m >> r >> c;
const int dx[] = {r, r, c, c}, dy[] = {c, -c, r, -r};
T = n * m * 2 + 2;
rep(i, 1, n) scanf("%s", s[i] + 1);
rep(i, 1, n) rep(j, 1, m) if(s[i][j] == '.') {
++cnt;
int u = id(i, j);
add(0, ou(u), 1), add(ou(u), 0, 0);
add(in(u), T, 1), add(T, in(u), 0);
rep(k, 0, 3) {
int x = i + dx[k], y = j + dy[k];
if(x < 1 || y < 1 || x > n || y > m) continue;
int v = id(x, y);
add(ou(u), in(v), 1), add(in(v), ou(u), 0);
}
}
cout << cnt - dinic();
return 0;
}
6. Luogu P4311 士兵占领
将行列看做点,将点看做边,问题转化为选择最少的边,覆盖每个点至少一定次数。发现这个东西和二分图最小边覆盖很相似,套用上文对最小边覆盖的证明即可得出,答案是
点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define per(i, s, e) for(int i = s, i##E = e; i >= i##E; --i)
#define F first
#define S second
// #define int ll
#define gmin(x, y) (x = min(x, y))
#define gmax(x, y) (x = max(x, y))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double f128;
typedef pair<int, int> pii;
constexpr int N = 105;
int n, m, k, T, r[N], c[N];
bool ava[N][N];
int hd[N * 2], to[N * N * 2], nxt[N * N * 2], cap[N * N * 2], tot = 1;
int dep[N * 2], cur[N * 2];
void add(int u, int v, int w) {
to[++tot] = v;
nxt[tot] = hd[u];
cap[tot] = w;
hd[u] = tot;
}
bool bfs() {
memset(dep, 0, sizeof dep);
dep[0] = 1;
queue<int> q;
q.push(0);
while(!q.empty()) {
int u = q.front();
q.pop();
cur[u] = hd[u];
for(int i = hd[u]; i; i = nxt[i]) {
int v = to[i];
if(cap[i] && !dep[v])
dep[v] = dep[u] + 1, q.push(v);
}
}
return dep[T];
}
int dfs(int u, int flow) {
if(u == T || !flow) return flow;
int res = 0;
for(int i = cur[u]; i; i = nxt[i]) {
cur[u] = i;
int v = to[i];
if(dep[v] == dep[u] + 1) {
int o = dfs(v, min(cap[i], flow));
flow -= o, res += o;
cap[i] -= o, cap[i ^ 1] += o;
}
}
return res;
}
int dinic() {
int ans = 0;
while(bfs()) ans += dfs(0, INT_MAX);
return ans;
}
signed main() {
#ifdef ONLINE_JUDGE
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> n >> m >> k;
T = n + m + 1;
int sum = 0;
rep(i, 1, n) cin >> r[i], sum += r[i], add(0, i, r[i]), add(i, 0, 0);
rep(i, 1, m) cin >> c[i], sum += c[i], add(i + n, T, c[i]), add(T, i + n, 0);
rep(i, 1, k) {
int u, v; cin >> u >> v;
++r[u], ++c[v]; ava[u][v] = 1;
if(m < r[u]) cout << "JIONG!\n", exit(0);
if(n < c[v]) cout << "JIONG!\n", exit(0);
}
rep(i, 1, n) rep(j, 1, m) if(!ava[i][j])
add(i, j + n, 1), add(j + n, i, 0);
cout << sum - dinic() << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?