二分图
定义
对于一张无向图
- 二分图的划分不唯一,也不一定联通,也不一定有环
存在的充要条件
若无向图
若无向图
判定
染色法。
枚举没有访问的点
所有点染色成功,则是一张二分图。
代码:
inline bool dfs(int x, int Col) {
col[x] = Col;
for(auto u : g[x]) {
if(col[x] == col[u]) return false;
if(! col[u]) return dfs(u, -Col);
}
return true;
}
例题
UVA10004 Bicoloring
模板题。
P1525 [NOIP2010 提高组] 关押罪犯
将监狱看成二分图的左右两部。
由于题目要求最大影响力最小,不难想到二分答案。又因为二分图有其判定算法,所以可以很好的结合二分。
二分当前的答案,将影响力大于当前二分值的关系建边(此处值得仔细思考),二分图判定即可。
代码:
inline bool dfs(int x, int Col) {
col[x] = Col;
for(auto u : g[x]) {
if(col[x] == col[u]) return false;
else if(! col[u] && dfs(u, -Col) == false) return false;
}
return true;
}
inline bool check(int x) {
for(int i = 1 ; i <= n ; ++ i)
col[i] = 0, g[i].clear();
for(int i = 1 ; i <= m ; ++ i)
if(w[i] > x) g[u[i]].pb(v[i]), g[v[i]].pb(u[i]);
for(int i = 1 ; i <= n ; ++ i)
if(! col[i])
if(! dfs(i, 1)) return false;
return true;
}
匹配
定义
对于一张二分图
最大匹配
定义
选取的最大边集
性质
-
不一定唯一
-
任意一条边一定在不同点集内
-
有一些点和边必选,有一些可选
求取:匈牙利算法(Hungarian Algorithm)
1.枚举左部的点
2.若到达右部的点
3.若点
4.重复执行
5.时间复杂度
代码:
inline bool hungary(int x) {
for(auto u : g[x]) {
if(! vis[u]) {
vis[u] = true;
if(! mch[u] || hungary(mch[u])) {
mch[u] = x;
return true;
}
}
}
return false;
}
注意建的是单向边。
建模特征
不共点,边最多。
例题
P3386 【模板】二分图最大匹配
模板题。
P10937 車的放置
好题。
难点在于将行、列看成二分图的左部与右部节点,把车看作一条边。
代码:
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N = 205;
int n, m, t, x, y, mch[N];
bool vis[N], ok[N][N];
vector<int> g[N];
inline bool hungary(int x) {
for(auto u : g[x])
if(! vis[u]) {
vis[u] = true;
if(! mch[u] || hungary(mch[u])) {
mch[u] = x;
return true;
}
}
return false;
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> m >> t;
for(int i = 1 ; i <= t ; ++ i) {
cin >> x >> y;
ok[x][y] = true;
}
for(int i = 1 ; i <= n ; ++ i)
for(int j = 1 ; j <= m ; ++ j)
if(! ok[i][j]) g[i].pb(j);
int ans = 0;
for(int i = 1 ; i <= n ; ++ i) {
memset(vis, false, sizeof vis);
if(hungary(i)) ++ ans;
}
cout << ans;
return 0;
}
P2055 [ZJOI2009] 假期的宿舍
一定要注意的是只有在校生有床,回家的人不需要床。
代码:
for(int i = 1 ; i <= n ; ++ i) {
for(int j = 1 ; j <= n ; ++ j) {
cin >> x;
if(x && iss[j]) g[i].pb(j);
else if(iss[i] && ! ish[i] && i == j) g[i].pb(i);
}
}
for(int i = 1 ; i <= n ; ++ i)
if((iss[i] && ! ish[i]) || ! iss[i]) ++ res;
int ans = 0;
for(int i = 1 ; i <= n ; ++ i) {
memset(vis, false, sizeof vis);
if(((iss[i] && ! ish[i]) || ! iss[i]) && hungary(i)) ++ ans;
}
if(ans >= res) cout << "^_^\n";
else cout << "T_T\n";
P2756 飞行员配对方案问题
注意
分辨清楚自己将什么当作左部什么当作右部。
代码:
for(int i = 1 ; i <= n ; ++ i) {
if(ok[mch[i]] || ! mch[i]) continue;
ok[mch[i]] = true;
cout << mch[i] << ' ' << i + m << '\n';
}
P2319 [HNOI2006] 超级英雄
一个不同寻常的建模方法。
将要求的答案作为一个点集去进行最大匹配,以后建模时要注意考虑到这一点。
代码:
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N = 1e3 + 5;
int n, m, u, v, mch[N], Ans[N];
bool vis[N];
vector<int> g[N];
inline bool dfs(int x) {
for(auto u : g[x]) {
if(! vis[u]) {
vis[u] = true;
if(! mch[u] || dfs(mch[u])) {
mch[u] = x;
return true;
}
}
}
return false;
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> m;
for(int i = 1 ; i <= m ; ++ i) {
cin >> u >> v;
++ u, ++ v;
g[i].pb(u), g[i].pb(v);
}
int ans = 0;
for(int i = 1 ; i <= m ; ++ i) {
memset(vis, false, sizeof vis);
if(dfs(i)) ++ ans;
else break;
}
cout << ans << '\n';
for(int i = 1 ; i <= n ; ++ i)
Ans[mch[i]] = i;
for(int i = 1 ; i <= ans ; ++ i)
cout << Ans[i] - 1 << '\n';
return 0;
}
最小点覆盖
定义
对于一张二分图
性质
-
中任意一条边至少有一个端点在点集内 -
点集选取不一定唯一
-
可能存在一条边的两个端点都在点集内
-
最小点覆盖大小 = 最大匹配大小
对于性质 的证明
假设最小点覆盖大小为
由于
在点集内每个点都可以组成一个匹配,共
取交集,得:
证毕。
建模特征
“至少二选一”。
例题
UVA1194 Machine Schedule
1.每个人物的两个模式至少有一个需要重启得到
2.重启次数最少,等价于选取模式最少
3.用最少的模式覆盖所有的任务 + 至少二选一 —— 最小点覆盖
4.注意工作模式可以为
代码:
for(int i = 1 ; i <= n ; ++ i)
mch[i] = 0, g[i].clear();
cin >> n;
if(! n) return 0;
cin >> m >> k;
for(int i = 1 ; i <= k ; ++ i) {
cin >> u >> u >> v;
if(u && v) g[u].pb(v);
}
int ans = 0;
for(int i = 1 ; i < n ; ++ i) {
memset(vis, false, sizeof vis);
if(dfs(i)) ++ ans;
}
cout << ans << '\n';
P6062 [USACO05JAN] Muddy Fields G
题意:不能盖住草地,需要盖住所有泥地,允许重叠。
1.木板长度要尽可能的少,长度就要尽可能的长(反证)
2.木板分为横向和纵向,长度取决于连续泥地的数量
3.一块泥地需要被横向或(而且)纵向的木板盖住,至少二选一
4.把行和列视为二分图的左部和右部,每一块泥地视为边
5.预处理横向和纵向的泥地连通块,并编号
6.连通块之间建边即可跑最小点覆盖即可
代码:
tot = 1;
for(int i = 1 ; i <= n ; ++ i)
for(int j = 1 ; j <= m + 1 ; ++ j)
if(c[i][j] == '*') id[i][j][0] = tot;
else ++ tot;
int p = tot - 1;
for(int j = 1 ; j <= m ; ++ j)
for(int i = 1 ; i <= n + 1 ; ++ i)
if(c[i][j] == '*') id[i][j][1] = tot;
else ++ tot;
for(int i = 1 ; i <= n ; ++ i)
for(int j = 1 ; j <= m ; ++ j)
if(c[i][j] == '*') g[id[i][j][0]].pb(id[i][j][1]);
int ans = 0;
for(int i = 1 ; i <= p ; ++ i) {
memset(vis, false, sizeof vis);
if(dfs(i)) ++ ans;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!