本篇博客不提供基本二分图匹配算法(匈牙利算法、网络流)
二分图最小点覆盖
给定一个二分图,求出最小的点集S,使得图中任意一条边都至少有一个端点属于S。这个点集,就是二分图最小点覆盖
Konig定理:
二分图的最小点覆盖包含的点数等于二分图的最大匹配包含的边数。
这样就让我们可以用二分图匹配算法来解此类问题。
例1:poj-1325 Machine Schedule
二分图最小点覆盖模型的主要两个要素(实际一种):
1.每个边都有两个端点(废话)
2.一个边的两个端点至少选一个。
这种抽象模型倒是让我想起了以前做的一个树形dp的题。这个题也符合这个模型,但是它是树形的,可以使用树形dp,对于更广泛的二分图,不适用的。
所以真的例1,我们将机器的模式a[i],b[i]当成二分图的两个点集,每一个任务则是一条边。则可以看出符合这个模型。
下面是ac代码:
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <map>
#include <queue>
#define ll long long
using namespace std;
const int N = 1e5+5;
int tot;
int he[2024], ne[2024], ver[2024];
int ho[2024], gg[2024], match[2024];
bool vis[2024];
int n, m, k;
int ans;
void add(int x, int y)
{
ver[++tot] = y;
ne[tot] = he[x];
he[x] = tot;
}
void init()
{
memset(vis, 0, sizeof(vis));
memset(he, 0, sizeof(he));
tot = 1;
ans = 0;
memset(match, 0, sizeof(match));
}
bool dfs(int x)
{
for (int i = he[x]; i; i = ne[i])
{
int y = ver[i];
if (!vis[y])
{
vis[y] = 1;
if (!match[y] || dfs(match[y]))
{
match[y] = x;
return 1;
}
}
}
return 0;
}
void getans()
{
for (int i = 1; i <= n; i++)
{
memset(vis, 0, sizeof(vis));
if (dfs(i)) ans++;
}
}
int main()
{
while(scanf("%d", &n), n)
{
init();
scanf("%d%d", &m, &k);
for (int i = 0; i < k; i++)
{
int te;
int a, b;
scanf("%d%d%d", &te, &a, &b);
if (a == 0 || b == 0) continue;
add(a, b + n);
}
getans();
printf("%d\n", ans);
}
return 0;
}
ps.一个被我忽略的地方,跑匈牙利,只要从一个点集建边至另一个点集的单向边dfs第一个点集即可。
例2:poj-2226 Muddy Fields
在这个题中,我们认定:每个点,都要被一个竖着的木板或者横着的木板盖上。我们则把这个点所属的连续横块和连续竖块连边,随有模型。
下面是ac代码:
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <map>
#include <queue>
#define ll long long
using namespace std;
const int N = 1e5+5;
int tot;
int he[2024], ne[2024], ver[2024];
int ho[2024], gg[2024], match[2024];
char mp[64][64];
int mpn[64][64], mpm[64][64];
int cntn, cntm;
bool vis[2024];
int n, m, k;
int ans;
void add(int x, int y)
{
ver[++tot] = y;
ne[tot] = he[x];
he[x] = tot;
}
void init()
{
memset(vis, 0, sizeof(vis));
memset(he, 0, sizeof(he));
tot = 1;
ans = 0;
cntn = cntm = 0;
memset(match, 0, sizeof(match));
}
bool dfs(int x)
{
for (int i = he[x]; i; i = ne[i])
{
int y = ver[i];
if (!vis[y])
{
vis[y] = 1;
if (!match[y] || dfs(match[y]))
{
match[y] = x;
return 1;
}
}
}
return 0;
}
void getans(int nn)
{
for (int i = 1; i <= nn; i++)
{
memset(vis, 0, sizeof(vis));
if (dfs(i)) ans++;
}
}
int main()
{
while(~scanf("%d%d", &n, &m))
{
init();
for (int i = 1; i <= n; i++)
scanf("%s", mp[i]+1);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (mp[i][j] == '*' &&( mp[i][j-1] == '.' || mp[i][j-1] == 0))
cntn++;
mpn[i][j] = cntn;
}
}
cntm = cntn;
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= n; j++)
{
if (mp[j][i] == '*' &&( mp[j-1][i] == '.' || mp[j-1][i] == 0))
cntm++;
mpm[j][i] = cntm;
}
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (mp[i][j] == '*') add(mpn[i][j], mpm[i][j]);
getans(cntn);
printf("%d\n", ans);
}
return 0;
}
二分图最大独立集与最大团
图的独立集就是“任意两点之间都没有边相连”的点集。
图的团就是“任意两点之间均有边相连”的点集。
对于一个一般图的最大独立集与最大团,目前没有现行有效的算法求出,只有通过暴力搜索。不过这里有几条
定理1:
无向图的最大团等于其补图的最大独立集。
定理2:
对于一个二分图,其最大独立集等于:该图的顶点数减最大匹配数。
例3:牛客 骑士放置
我们观察国际象棋:骑士每次走,都垮两种颜色。故我们把2*3的每个格子的对角线用边链接,构成的图为二分图。我们直接跑一边匈牙利即可。
下面是ac代码:
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <map>
#include <queue>
#define ll long long
using namespace std;
const int N = 1e5+5;
int tot =1;
int he[80024], ne[80024], ver[80024];
int ho[80024], gg[80024], match[80024];
bool vis[20240];
bool mp[128][128];
int ff[128][128];
int n, m, t;
int cnt;
int ans;
void add(int x, int y)
{
ver[++tot] = y;
ne[tot] = he[x];
he[x] = tot;
}
void init()
{
memset(vis, 0, sizeof(vis));
memset(he, 0, sizeof(he));
memset(mp, 0, sizeof(mp));
memset(ff, 0, sizeof(ff));
ans = 0;
cnt = 0;
memset(match, 0, sizeof(match));
}
bool dfs(int x)
{
for (int i = he[x]; i; i = ne[i])
{
int y = ver[i];
if (!vis[y])
{
vis[y] = 1;
if (!match[y] || dfs(match[y]))
{
match[y] = x;
return 1;
}
}
}
return 0;
}
void getans(int nn)
{
for (int i = 1; i <= nn; i++)
{
memset(vis, 0, sizeof(vis));
if (dfs(i)) ans++;
}
}
inline bool che(int x, int y)
{
return x >= 1 && x <= n && y >= 1 && y <= m;
}
int main()
{
scanf("%d%d%d", &n, &m, &t);
init();
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (!((i + j)&1)) ff[i][j] = ++cnt;
}
}
int mx = cnt;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if ((i+j)&1) ff[i][j] = ++cnt;
}
}
for (int i = 0; i < t; i++)
{
int x, y;
scanf("%d%d", &x, &y);
mp[x][y] = 1;
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (mp[i][j]) continue;
if (!((i+j)&1))
{
if (che(i+1, j+2) && !mp[i+1][j+2]) add(ff[i][j], ff[i+1][j+2]);
if (che(i+2, j+1) && !mp[i+2][j+1]) add(ff[i][j], ff[i+2][j+1]);
if (che(i+1, j-2) && !mp[i+1][j-2]) add(ff[i][j], ff[i+1][j-2]);
if (che(i+2, j-1) && !mp[i+2][j-1]) add(ff[i][j], ff[i+2][j-1]);
}
else
{
if (che(i+1, j+2) && !mp[i+1][j+2]) add(ff[i+1][j+2], ff[i][j]);
if (che(i+2, j+1) && !mp[i+2][j+1]) add(ff[i+2][j+1], ff[i][j]);
if (che(i+1, j-2) && !mp[i+1][j-2]) add(ff[i+1][j-2], ff[i][j]);
if (che(i+2, j-1) && !mp[i+2][j-1]) add(ff[i+2][j-1], ff[i][j]);
}
}
}
getans(mx);
printf("%d\n", n*m-t-ans);
return 0;
}
有向无环图的最小路径点覆盖
给定一张有向无环图,要求用尽量少的不相交的简单路径,覆盖所有点。
满足这种模型的,我们如下处理:
我们把原图
G
G
G中的所有点拆成两个点
x
,
x
+
n
x, x+n
x,x+n,(以1~n为一部, n +1 ~2n为另一部)建立一个二分图
G
2
G_2
G2。
这时我们有:
定理
有向无环图 G G G的最小路径点覆盖等于 G 2 G_2 G2的最大匹配。
例4:牛客 Vani和cl2捉迷藏
我们求出最小路径点覆盖,每个路径上只能选择一个点。
下面是ac代码(来自蓝皮书):
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <map>
#include <queue>
#define ll long long
using namespace std;
bool cl[222][222];
int match[222],n,m;
bool vis[222], succ[222];
int hide[222];
int ans;
bool dfs(int x)
{
for (int i = 1; i <= n; i++)
{
if (cl[x][i] && !vis[i])
{
vis[i] = 1;
if (!match[i] || dfs(match[i]))
{
match[i] = x;
return 1;
}
}
}
return 0;
}
void getans(int n)
{
ans = n;
for (int i = 1; i <= n; i++)
{
memset(vis, 0, sizeof(vis));
ans -= dfs(i);
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
int x,y;
scanf("%d%d", &x, &y);
cl[x][y] = 1;
}
for (int i = 1; i <= n; i++) cl[i][i] = 1;
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cl[i][j] |= cl[i][k] && cl[k][j];
for (int i = 1; i <= n; i++)
cl[i][i] = 0;
getans(n);
printf("%d\n", ans);
return 0;
}
二分图的最小边覆盖
对于一个二分图,我们选择一些边,是这些边覆盖所有的点,那么,我们最少要选择:
点总数-最大匹配数