二分图相关定理

本篇博客不提供基本二分图匹配算法(匈牙利算法、网络流)

二分图最小点覆盖

给定一个二分图,求出最小的点集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;
}

二分图的最小边覆盖

对于一个二分图,我们选择一些边,是这些边覆盖所有的点,那么,我们最少要选择:

点总数-最大匹配数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值