『毒瘤算法系列11』二分图(二分图匹配·强连通分量)

P r o b l e m \mathrm{Problem} Problem

给定一个两侧各有 n n n m m m个点的二分图(保证 n ≤ m n≤m nm),对于每条边,你需要判断原图是否存在一个大小为 n n n,且包含了这条边的匹配。

S o l u t i o n \mathrm{Solution} Solution

首先我们需要进行二分图匹配,若匹配 < n <n时直接判定无解。

我们首先思考一下在 n = m n=m n=m时存在匹配的情况:

  • 右边的每一个点都能在左边找到一个匹配。
    『毒瘤算法系列11』二分图(二分图匹配·强连通分量)_第1张图片
  • 左边的每一个点如果需要通过通非匹配边再找到一组合法解的话,我们肯定是要拆散原来哪些合法解的。
    『毒瘤算法系列11』二分图(二分图匹配·强连通分量)_第2张图片
    这些组合中的点和其它点都是独立的,我们需要思考如何通过某种方式找出这些点。

观察到它和匹配点恰好形成了一个有规则的图形。恰好经过了匹配边,非匹配边,匹配边和非匹配边最后回到自己。那么我们可以采取如下建图方式:

  • 若边 ( i , j ) (i,j) (i,j)是匹配边,从 i i i j j j连一条边。
  • 若边 ( i , j ) (i,j) (i,j)是非匹配边,从 j j j i i i连一条边。
  • 这样,我们只要一条目前处于非匹配状态的边 ( i , j ) (i,j) (i,j)想成为匹配边,充要条件为 i i i j j j处于同一个强连通分量内。
  • 因为这相当于强联通分量内形成环, i i i依次经过匹配边, ( i , j ) (i,j) (i,j)这一非匹配边…,匹配边,非匹配边走回自己。那么同样可以形成一种匹配方案经过的相当边,将边的左右顺序调换(因为强联通分量内环有可逆性),这样就形成了另外的一种方案。

所以再 n = m n=m n=m的时候只需要求解强连通分量判断两点是不是在同一连通分量内即可完成判定。

但是若 n < m nn<m时右边不一定会每一个点都有向左连接的边呢?
例如这是一组匹配方案:
『毒瘤算法系列11』二分图(二分图匹配·强连通分量)_第3张图片
观察到红边同样可以成为一组合法的匹配方案,但是为什么不满足强联通分量的特性呢?

这是因为右下角缺少能够回到左边的边,因为我们可以得到类似的做法:

  • 知道一个虚拟节点,用匹配边的右边点连它。用它连向非匹配边的左边点
    『毒瘤算法系列11』二分图(二分图匹配·强连通分量)_第4张图片
  • 这样,我们可以得到一个方案是匹配边,虚边,虚边和非匹配边;倒过来就是非匹配边,虚边,虚边和匹配边;那么,我们倒过来就可以得到一组合法的方案。
  • 因此,我们就可以用同样的强连通分量算法去判定合法性了。

C o d e \mathrm{Code} Code

#include 

using namespace std;
const int N = 3000;

int n, m, Dfn(0), cnt(0), top(0), scc(0);
int a[N][N], match[N], vis[N], dfn[N], low[N], color[N], in[N], st[N];
vector < int > G[N];

bool Dfs(int x)
{
	for (int i=n+1;i<=n+m;++i)	
	if (a[x][i])
	{
		if (vis[i] == Dfn) continue;
		vis[i] = Dfn;
		if (match[i] == 0 || Dfs(match[i])) {
			match[x] = i;
			match[i] = x;
			return 1;
		} 
	}
	return 0;
} 

void Make_Edge(void)
{
	for (int i=1;i<=n;++i)
	{
		for (int j=n+1;j<=n+m;++j)
			if (a[i][j]) 
			{
				if (match[i] == j) G[i].push_back(j);
				if (match[i] != j) G[j].push_back(i);
			}
	}
	return;
}

void Make_Edge1(void)
{
	for (int i=n+1;i<=n+m;++i)
	{
		if (match[i]) G[i].push_back(n+m+1);
		if (!match[i]) G[n+m+1].push_back(i);
	}
	return;
}

void tarjan(int x)
{
	low[x] = dfn[x] = ++ cnt;
	st[++ top] = x, in[x] = 1;
	for (int i=0;i<G[x].size();++i)
	{
		int y = G[x][i];
		if (dfn[y] == 0) {
			tarjan(y);
			low[x] = min(low[x],low[y]);
		}
		else if (in[y])
			low[x] = min(low[x],dfn[y]);
	}
	if (low[x] == dfn[x])
	{
		scc ++; int y;
		do 
		{
			y = st[top --];
			color[y] = scc;
			in[y] = 0;
		} while (x != y); 
	}
	return;
}

int main(void)
{
	cin >> n >> m;
	for (int i=1;i<=n;++i)
		for (int j=1;j<=m;++j)
			scanf("%1d", a[i]+j+n);
	for (int i=1;i<=n;++i)
	if (match[i] == 0) 
	{
		Dfn ++;
		Dfs(i);
	}
	int res = 0;
	for (int i=1;i<=n;++i)
		res += match[i] > 0;
	if (res < n) 
	{
		for (int i=1;i<=n;++i)
			for (int j=1;j<=m;++j)
				printf("%d%s", 1, j==m?"\n":"");
		exit(0);
	}
	Make_Edge();
	if (n < m) Make_Edge1();
	for (int i=1;i<=n+m+1;++i)
		if (dfn[i] == 0) tarjan(i);
	for (int i=1;i<=n;++i)
	{
		for (int j=n+1;j<=n+m;++j)
			if (a[i][j] && (match[i] == j || color[i] == color[j]))
				printf("0");
			else printf("1");
		printf("\n");
	}
	return 0;	
}

你可能感兴趣的:(毒瘤算法)