二分图匹配
二分图匹配
定义#
-
二分图:是一张可以将结点分为两个集合,且每个集合内部不存在连边的图。
-
交错路:始于非匹配点且由匹配边与非匹配边交错而成。
-
增广路:是始于非匹配点且终于非匹配点(除了起始的点)的交错路。增广路中边的数量是奇数。
二分图最大匹配#
例题:CSES - 1696#
二分图最大匹配就是要在边集中选出一些边,使得这些边没有公共顶点,且边的数量最多。
匈牙利算法#
由于增广路的性质,我们会发现,当我们找到一条增广路的时候,这条增广路上的非匹配边一定比匹配边多一条,那么我们可以直接放弃选择原本的匹配边,而是改成选择原本的非匹配边,这样匹配的边还会多一条。
由于二分图的所有结点可以分为两个集合,我们称这两个集合为
事实上就是在找增广路径。
bool dfs(int u) {
if (vis[u]) return 0;
vis[u] = 1;
for (int v : g[u]) {
if (!pr[v] || dfs(pr[v])) {
pl[u] = v, pr[v] = u;
return 1;
}
}
return 0;
}
Dinic#
我们可以把二分图匹配转换成最大流的模型。
其中用方框框出来的部分是原本的二分图。
我们这样考虑,由于每个点只能被选择一次,因此,对于每一个
同样的,对于每一个
像这样建出图,然后直接跑 dinic
即可。
void addEdge(int u, int v, int w) {
int x = g[u].size(), y = g[v].size();
g[u].push_back({v, w, y});
g[v].push_back({u, 0, x});
}
bool bfs() {
fill(d, d + N, -1);
fill(vis, vis + N, 0);
d[s] = 0, que.push(s);
while (!que.empty()) {
int u = que.front(); que.pop();
for (auto [v, w, id] : g[u]) {
if (w > 0 && d[v] == -1) {
d[v] = d[u] + 1, que.push(v);
}
}
}
return d[t] != -1;
}
ll dfs(int u, ll f) {
if (u == t) return f;
if (vis[u]) return 0;
ll ret = 0;
for (auto &[v, w, id] : g[u]) {
if (w <= 0 || d[v] != d[u] + 1) continue;
ll tmp = dfs(v, min(f, 0ll + w));
auto &[_, vw, __] = g[v][id];
ret += tmp, vw += tmp, f -= tmp, w -= tmp;
if (f <= 0) break;
}
vis[u] = f > 0;
return ret;
}
最小点覆盖#
对于图
最小点覆盖
最大独立集#
对于图
最大独立集
CF1765A#
题意#
有
为了限制访问,你需要将这
同时,你需要给每个人分配
具体来说就是对于属于第
请你求出
思路#
我们考虑两份文档在什么情况下可以被分到同一个小组,我们很容易发现,当可以访问
因此,我们将每对这样的
然后,我们再做一遍拓扑排序,求出每份文件的访问级别,最后算出每个人的访问级别即可。
代码#
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int N = 5e5 + 10, M = 510;
struct Node {
int v, w, id;
};
int T, n, m, k, d[N], s, t, p[M][M], id[M], mp[N], cnt;
int deg[M], nxt[M], col[M], val[N], ans[M], res;
bool vis[N], a[M][M];
vector<Node> g[N];
queue<int> que;
void addEdge(int u, int v, int w) {
int x = g[u].size(), y = g[v].size();
g[u].push_back({v, w, y});
g[v].push_back({u, 0, x});
}
bool bfs() {
fill(d, d + N, -1);
fill(vis, vis + N, 0);
d[s] = 0, que.push(s);
while (!que.empty()) {
int u = que.front(); que.pop();
for (auto [v, w, id] : g[u]) {
if (w > 0 && d[v] == -1) {
d[v] = d[u] + 1, que.push(v);
}
}
}
return d[t] != -1;
}
int dfs(int u, int f) {
if (u == t) return f;
if (vis[u]) return 0;
ll ret = 0;
for (auto &[v, w, id] : g[u]) {
if (w <= 0 || d[v] != d[u] + 1) continue;
ll tmp = dfs(v, min(f, w));
auto &[_, vw, __] = g[v][id];
ret += tmp, vw += tmp, f -= tmp, w -= tmp;
if (u <= cnt && v >= cnt && !w) nxt[u] = v - cnt, deg[v - cnt]++;
if (f <= 0) break;
}
vis[u] = f > 0;
return ret;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
char c; cin >> c, a[i][j] = c - '0';
}
}
for (int i = 1; i <= m; i++) id[i] = i;
for (int i = 1; i <= m; i++) {
for (int j = i + 1; j <= m; j++) {
bool flag1 = 1, flag2 = 1;
for (int k = 1; k <= n; k++) {
flag1 &= a[k][i] <= a[k][j]; // val[i] >= val[j]
flag2 &= a[k][j] <= a[k][i]; // val[i] <= val[j]
}
if (flag1 && flag2) id[j] = id[i];
else if (flag1 || flag2) p[i][j] = (flag1 ? 1 : 2);
}
}
for (int i = 1; i <= m; i++) {
if (!mp[id[i]]) mp[id[i]] = ++cnt;
}
for (int i = 1; i <= m; i++) {
for (int j = i + 1; j <= m; j++) {
int u = mp[id[i]], v = mp[id[j]];
if (p[i][j] == 1) addEdge(u, v + cnt, 1);
else if (p[i][j] == 2) addEdge(v, u + cnt, 1);
}
}
t = 2 * cnt + 1;
for (int i = 1; i <= cnt; i++) {
addEdge(s, i, 1), addEdge(i + cnt, t, 1);
val[i] = 1;
}
while (bfs()) res += dfs(s, 1e9);
cout << cnt - res << '\n';
for (int i = 1; i <= cnt; i++) {
if (!deg[i]) {
int x = i; k++, val[x] = 1e9;
while (x) {
val[nxt[x]] = val[x] - 1;
col[x] = k, x = nxt[x];
}
}
}
for (int i = 1; i <= m; i++) cout << col[mp[id[i]]] << ' ';
cout << '\n';
for (int i = 1; i <= m; i++) cout << val[mp[id[i]]] << ' ';
cout << '\n';
for (int i = 1; i <= n; i++) {
fill(ans + 1, ans + k + 1, 1);
for (int j = 1; j <= m; j++) {
if (a[i][j]) ans[col[mp[id[j]]]] = max(ans[col[mp[id[j]]]], val[mp[id[j]]]);
}
for (int i = 1; i <= k; i++) cout << ans[i] << ' ';
cout << '\n';
}
return 0;
}
CF1404E#
题意#
有一个
你有很多块砖,砖块被定义为具有整数边长的矩形,宽度为
你需要用砖块覆盖图上的每个黑色格子,但是不能覆盖到白色格子,也不能让某个黑色方格被两块砖块覆盖到。
请你求出最少需要用多少块砖。
思路#
我们考虑对于某个黑色方格,如果它的上方也是黑色方格,那么它们俩就是可以被同一块砖所覆盖的,就可以建出一条边;同样的,如果它的右边也是黑色方格,那么它们俩也是可以被同一块砖所覆盖的,也可以建出一条边。
但是,显然的,如果它的上方和右边都有黑色方格,它是无法同时和两个方格被一块砖覆盖的,也就是说,那两条边是无法同时选择的。
因此,我们可以在这两边中建一条边,意味着这两条边无法被同时选择。
我们又希望最终的砖块数最少,也就是说,我们希望被选择的边最多。
因此,答案就是这张图的最大独立集。
代码#
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 10, M = 210;
const int dx[] = {0, 1};
const int dy[] = {1, 0};
struct Node {
int v, w, id;
};
int T, n, m, d[N], s, t, cnt, k;
ll ans;
char a[M][M];
bool vis[N];
vector<int> p, c[M][M];
vector<Node> g[N];
queue<int> que;
void addEdge(int u, int v, int w) {
int x = g[u].size(), y = g[v].size();
g[u].push_back({v, w, y});
g[v].push_back({u, 0, x});
}
bool bfs() {
fill(d, d + N, -1);
fill(vis, vis + N, 0);
d[s] = 0, que.push(s);
while (!que.empty()) {
int u = que.front(); que.pop();
for (auto [v, w, id] : g[u]) {
if (w > 0 && d[v] == -1) {
d[v] = d[u] + 1, que.push(v);
}
}
}
return d[t] != -1;
}
ll dfs(int u, ll f) {
if (u == t) return f;
if (vis[u]) return 0;
ll ret = 0;
for (auto &[v, w, id] : g[u]) {
if (w <= 0 || d[v] != d[u] + 1) continue;
ll tmp = dfs(v, min(f, 0ll + w));
auto &[_, vw, __] = g[v][id];
ret += tmp, vw += tmp, f -= tmp, w -= tmp;
if (f <= 0) break;
}
vis[u] = f > 0;
return ret;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j], k += a[i][j] == '#';
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i][j] == '.') continue;
int nx = i, ny = j + 1;
if (1 <= nx && nx <= n && 1 <= ny && ny <= m && a[nx][ny] == '#') {
c[i][j].push_back(++cnt), c[nx][ny].push_back(cnt), addEdge(s, cnt, 1);
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i][j] == '.') continue;
int nx = i + 1, ny = j;
if (1 <= nx && nx <= n && 1 <= ny && ny <= m && a[nx][ny] == '#') {
cnt++;
for (int x : c[i][j]) addEdge(x, cnt, 1);
for (int x : c[nx][ny]) addEdge(x, cnt, 1);
p.push_back(cnt);
}
}
}
cnt++, t = cnt;
for (int x : p) addEdge(x, t, 1);
while (bfs()) ans += dfs(s, 1e18);
cout << k - (cnt - 1 - ans) << '\n';
return 0;
}
作者:cn
出处:https://www.cnblogs.com/chengning0909/p/18402382
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效