首页 > 其他分享 >二分图(一)

二分图(一)

时间:2023-01-04 22:34:34浏览次数:46  
标签:二分 VC 匹配 int 路径 MAXN

二分图(一)

\(\newcommand \m \mathbf\)

\(\text{By DaiRuiChen07}\)

一、二分图匹配基础

I. 匹配的概念

在一张无向图 \(\m G=(\m V,\m E)\) 中,如果 \(\m M\subseteq \m E\) 是 \(\m G\) 的一个匹配,当且仅当 \(\m M\) 中没有两条边有相同的顶点

以下记 \(V(\m M)\) 表示 \(\m M\) 中的边的顶点构成的点集

一个匹配的大小定义为 \(|\m M|\)

类似的,在一张带权无向图上,\(\m M\) 的权值为 \(\sum_{e\in\m M}w(e)\),其中 \(w(e)\) 表示 \(e\) 的边权

II. 增广轨与交错轨

交错轨是 \(\m G\) 中的一条简单路径,其中路径上在 \(\m M\) 中和不在 \(\m M\) 的边交替出现

增广轨(Augmenting Path)是一种特殊的交错轨,满足其两端的边和其两端的顶点都不在 \(\m M\) 中

增广轨有一个很优秀的性质:

如果令某条增广轨为 \(\m A\),那么 \(\m M\oplus\m A\) 也是一个匹配

且 \(|\m M\oplus \m A|=|\m M|+1\)

二、增广轨算法

I. 算法流程

xKcOjH.png

以上为代码不仅适用于二分图的最大匹配,同时也适用于一般图的最大匹配

II. 正确性证明

我们需要证明,当 \(\m G\) 中没有 \(\m M\) 的增广轨的时候,\(\m M\) 为 \(\m G\) 的一个最大匹配

证:

反证法,假设存在另一个匹配 \(\m {M'}\) 使得 \(|\m M'|>|\m M|\)

考虑一张子图 \(\m{G'}=\m{M'}\oplus\m M\),注意到其中的每个点度数最多为 \(2\),因此 \(\m G'\) 只能由若干环、链和点组成

显然,由于 \(\m {G'}\) 中的若干个连通块都必然是交替出现 \(\m M\) 和 \(\m M'\) 中的边,因此 \(\m {G'}\) 中不可能出现奇环

注意到:在一个偶环中,\(\m M\) 中的边和 \(\m {M'}\) 中的的边的数量是相等的

因此 \(\m G'\) 中必然要有一条链,使得其头尾的边都在 \(\m{M'}\) 中

那么此时这条链会构成一条交错路 \(\m A\),要证明 \(\m A\) 是 \(\m M\) 的一条增广轨,我们可以还需要证明 \(\m A\) 的两端不在 \(V(\m M)\) 中

这是显然的,因为如果其中一端存在一条边 $e\in\m M,e\not\in\m{G'} $ 那么我们可以推知 \(e\in \m{M’}\),但是此时这个端点在 \(\m {M'}\) 中引出了两条边,显然是不成立的

因此此时存在一条关于 \(\m M\) 的增广轨,故原命题得证

III. 如何找增广轨

令二分图 \(\m G=(\m X,\m Y,\m E)\),其中 \(\m X\) 为左部点点集(点为 \(\{x_i\}\)),\(\m Y\) 为右部点点集(点为 \(\{y_i\}\))

定义一个集合 \(\m S\) 维护此时某条交错轨的终点,朴素的算法流程如下:

xMRCMF.png

这样暴力找增广轨的复杂度 \(\Theta(n+m)\),由于我们一般认为 \(m\ge n\),所以这样的总复杂度是 \(\Theta(nm)\)

IV. 习题演练

SGU190 - Dominoes

考虑对于原本的棋盘做黑白间隔染色,那么每个骨牌一定覆盖两个相邻的异色格子,因此把黑白色的格子分别看成左部点和右部点,相邻的格子连边,可以变成一张二分图

然后只需要跑一遍二分图最大匹配,选择的边对应为覆盖其两个顶点的一个骨牌,只需要判断是否覆盖满了即可

注意到做法自动带匹配方案,因此输出很简单

代码如下:

#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int MAXN=1601,MAXS=41;
vector <int> edge[MAXN];
int n,k;
int tar[MAXN],dx[]={0,0,1,-1},dy[]={1,-1,0,0};
bool vis[MAXN],a[MAXS][MAXS];
inline int id(int x,int y) {
	if(x<1||x>n||y<1||y>n||a[x][y]) return -1;
	return (x-1)*n+y;
}
inline int getx(int id) {
	return ((id-1)/n)+1;
}
inline int gety(int id) {
	return id-(getx(id)-1)*n;
}
inline bool match(int p) {
	for(int x:edge[p]) {
		if(vis[x]) continue;
		vis[x]=true;
		if(tar[x]==-1||match(tar[x])) {
			tar[x]=p;
			return true;
		}
	}
	return false;
}
inline void print(vector <pii> data) {
	printf("%d\n",(int)data.size());
	for(auto x:data) printf("%d %d\n",x.first,x.second);
}
signed main() {
	memset(tar,-1,sizeof(tar));
	scanf("%d%d",&n,&k);
	for(int i=1;i<=k;++i) {
		int x,y;
		scanf("%d%d",&x,&y);
		a[x][y]=1;
	}
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n;++j) {
			if(id(i,j)==-1||(i+j)%2==0) continue;
			for(int k:{0,1,2,3}) {
				int x=i+dx[k],y=j+dy[k];
				if(id(x,y)!=-1) {
					edge[id(i,j)].push_back(id(x,y));
				}
			}
		}
	}
	int ret=0;
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n;++j) {
			if(id(i,j)==-1||(i+j)%2==0) continue;
			memset(vis,false,sizeof(vis));
			if(match(id(i,j))) ++ret;
		}
	}
	if(ret*2+k!=n*n) {
		puts("No");
		return 0;
	}
	puts("Yes");
	vector <pii> her,ver;
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n;++j) {
			if(id(i,j)==-1||(i+j)%2==1) continue;
			int x=getx(tar[id(i,j)]),y=gety(tar[id(i,j)]);
			if(x==i) her.push_back(make_pair(x,min(j,y)));
			else ver.push_back(make_pair(min(i,x),y));
		}
	}
	print(ver);
	print(her);
	return 0;
}

三、Hall 定理

I. 二分图的邻域

对于一张二分图 \(\m G=(\m X,\m Y,\m E)\)

对于点集 \(\m A\subseteq \m X\),\(N(\m A)=\{y_i|\exists x_j:(x_j,y_i)\in\m E\}\)

即所有与 \(\m A\) 中的某个点 \(x_j\) 相连的 \(y_i\) 组成的集合记为 \(N(\m A)\),即 \(\m A\) 的邻域

II. Hall 定理

\(\m G\) 中有一个大小为 \(|\m X|\) 的匹配 \(\iff\) \(\forall \m A\subseteq \m S:|N(\m A)|\ge |\m A|\)

III. 证明

充分性显然:\(N(\m A)\) 中一定包含所有 \(\m A\) 中通过匹配边连接的右部点,故 \(|N(\m A)|\ge|\m A|\)

下证必要性:

证:

原命题较难,考虑其逆否命题

即:对于 \(\m G\) 的最大匹配 \(\m M\),我们有 \(|\m X|>|\m M|\),即某个大小为 \(|\m X|\) 的边集 \(\m K\) 不是 \(\m G\) 的一个匹配

那么可以推出 \(\exists\m A\subseteq \m K:|N(\m A)|< |\m A|\)

考虑对匹配 \(\m M\) 运行一遍查找增广路的算法,那么此时得到上述的集合 \(\m S\)

我们记 \(\m{S_X}=\m S\cap \m X\),\(\m{T_X}=\overline{\m{S_X}}\),\(\m {S_Y}=\m S\cap\m Y\),\(\m{T_Y}=\overline{\m{S_Y}}\)

我们大概的思路是:

首先:证明 \(N(\m{S_X})=\m{S_Y}\),可以转化为不存在连接 \(\m{S_X}\) 和 \(\m{T_Y}\) 的边

然后,我们证明 \(|\m {S_X}|>|\m {S_Y}|\)

引理一:\(N(\m{S_X})=\m{S_Y}\)

  1. 不存在 \(\m M\) 中的,连接 \(\m{S_X}\) 和 \(\m{T_Y}\) 的边
    显然,考虑 \(\m {S_X}\) 的生成过程,如果某个 \(x\in\m{S_X}\),那么必然有以下两种情况之一:
  • \(x\) 没有连接 \(\m M\) 的边
  • \(x\) 通过某条 \(\m M\) 里面的边和某个 \(\m{S_Y}\) 里面的点相连

这两种情况都是不可能出现在某条连接 \(\m{S_X}\) 和 \(\m{S_Y}\) 的边当中的

  1. 不存在不在 \(\m M\) 中的,连接 \(\m{S_X}\) 和 \(\m{T_Y}\) 的边

考虑 \(\m{T_Y}\) 的生成过程,假如某个 \(y\in \m{T_Y}\) 考虑某一次从 \(\m{S_X}\) 中拓展若干个节点到 \(\m {S_Y}\) 的过程,有:

\(\not\exists (x,y)\in \m E:x\in\m{S_X}\),显然这样也是矛盾的

综上所述不存在连接 \(\m {S_X}\) 和 \(\m{S_Y}\) 的边

故 \(N(\m{S_X})=\m{S_Y}\)

引理二:\(|\m{S_Y}|<|\m {S_X}|\)

考虑到 \(\m{S_Y}\) 的生成过程,显然每个 \(y\in\m{S_Y}\) 都可以通过一条 \(\m M\) 中的边和某个 \(\m{S_X}\) 中的点相连,故 \(|\m {S_Y}|\le|\m{S_X}|\)

同时,由于我们的假设 \(|\m M|<|\m X|\),因此必然有某个 \(x\in\m{S_X}\) 使得所有与 \(x\) 相连的边都不在 \(\m M\) 中,那么我们就证明了 \(|\m{S_Y}|\) 严格小于 \(|\m {S_X}|\)

综合以上结论:我们可以证明出必要性的逆否命题成立,显然得到必要性的证明

综上所述,原命题得证

IV. 应用

一般来说,Hall 定理常用于证明某中特殊的二分图有完美匹配(满足 \(V(\m M)=\m V\) 的一个匹配)

例如:对于二分图 \(\m G=(\m X,\m Y,\m E)\),其中每个节点的度数都为 \(k\),求证:这个二分图一定有一个完美匹配(大小为 \(|\m X|\) 的一个匹配)

证:

根据 Hall 定理,原命题 \(\iff\) \(\forall \m A\subseteq\m X:|\m A|\le|N(\m A)|\)

采用反证法,假设存在 \(\m A\subseteq\m X:|\m A|>|N(\m A)|\)

易得 \(|N(\m A)|\) 中所有点的度的和为 \(k\cdot|\m A|\)

因此,至少存在一个点 \(y\in N(\m A)\) 使得 \(\operatorname{deg}(u)\ge\dfrac{k\cdot|\m A|}{|N(\m A)|}\)

注意到 \(\dfrac{k\cdot|\m A|}{|N(\m A)|}>\dfrac{k\cdot|\m A|}{|\m A|}=k\)

因此存在 \(u\in \m V:\operatorname{deg}(u)>k\),显然矛盾

故原命题得证

V. 习题演练

定义“拉丁方”是一个 \(n\times n\) 的矩阵,其中每一行每一列都是一个 \(1\sim n\) 的排列

现在这个拉丁方的前 \(k\) 行已经填好,且目前这个拉丁方没有出现不合法的地方

求证:无论这个拉丁方的前 \(k\) 行填成什么样,总是有至少一个解能够补全这个拉丁方使其完全合法

证明如下:

考虑对于第一列到第 \(n\) 列 \(C_1\sim C_n\) 当成左部点,而 \(1\sim n\) 当成右部点,如果目前第 \(i\) 列中没有出现过 \(j\) 就连接 \((C_i,j)\)

显然,这是一张二分图,且二分图上的某条边 \((C_i,j)\) 代表 \(k+1\) 行第 \(i\) 列填上 \(j\)

那么这张二分图上的一个大小为 \(n\) 的匹配就代表了第 \(k+1\) 行的某一种可能的填法

注意到二分图中每个点的度都是 \(n-k\),因此根据上面证明的 Hall 定理的推论,一定有一个大小为 \(n\) 的匹配

故我们可以得到第 \(k+1\) 行的一种合法填法,此时另 \(k\gets k+1\) 继续操作即可最终得到一个合法的拉丁方,证毕

实际上上面的证明给出了一种 \(\Theta(n^3)\) 填补完成拉丁方的一种做法

四、Konig 定理

I. 独立集

对于某个 \(\m X\subseteq \m V\),若满足 \(\forall u,v\in\m X:(u,v)\not\in \m E\)

即 \(\m X\) 中没有两个点直接相连,则称 \(\m X\) 是 \(\m V\) 的一个独立集(Independent Set,简称 IS)

II. 覆盖集

对于某个 \(\m X\in\m V\),若满足 \(\forall (u,v)\in \m E:u\in \m X\or v\in \m X\)

即每条边都至少与 \(\m X\) 中的一个点相连,则称 \(\m X\) 是 \(\m V\) 的一个覆盖集(Vertex Cover,简称 VC)

III. 最小覆盖集与最大独立集

记 \(\m{IS}\) 为 \(\m G\) 中的一个独立集,\(\m {VC}\) 为 \(\m G\) 中的一个覆盖集

则有 \(\overline{\m{IS}}\) 为一个 \(\m G\) 的覆盖集, \(\overline{\m{VC}}\) 为一个 \(\m G\) 的独立集

实际上这两者都等价于证明 \(\m {IS}\) 中没有连边,这是显然的

因此,如果我们记 \(|\m{IS}|\) 为 \(\m G\) 中最大独立集的大小,\(|\m{VC}|\) 为 \(\m G\) 中最小覆盖集的大小,那么由上可知 \(|\m{IS}|+|\m{VC}|=|\m V|\)

IV. Konig 定理

在一般图上求解最小覆盖集和最大独立集都是 NP - Completed 的,但在二分图上 \(\m{VC}\)(最小覆盖集)有很好的性质:

记 \(\m M\) 为 \(\m G\) 的最大匹配,那么 \(|\m {VC}|=|\m M|\)

V. 证明

考虑把 \(|\m{VC}|=|\m M|\) 拆分成 \(|\m {VC}|\ge |\m M|\) 和 \(|\m{VC}|\le |\m M|\) 两个命题即可

注意到 \(|\m {VC}|\ge|\m M|\) 是显然的,因为 \(\m M\) 中的每一条边都至少有一个端点在 \(\m {VC}\) 中,又因为 \(\m M\) 的边没有公共端点,我们可以得到 \(|\m M|\le|\m {VC}|\)

接下来证明 \(|\m{VC}|\le|\m M|\),事实上,我们只需要找到一个覆盖集 \(\m {VC'}\) 满足 \(|\m{VC'}|=|\m M|\) 就可以得出结论 \(|\m{VC}|\le|\m{VC'}|=|\m M|\)

类似 Hall 定理的证明过程,我们把 \(\m V\) 划分成 \(\m{S_X},\m{T_X},\m{S_Y},\m{T_Y}\) 四个点集

由 Hall 定理的引理一,我们能够知道不存在连接 \(\m{S_X}\) 和 \(\m{T_Y}\) 的边,因此 \(\m{T_X}\cup\m{S_Y}\) 一定是一个覆盖集,我们只需要证明 \(|\m{T_X}|+|\m{S_Y}|=|\m M|\) 即可

事实上,我们首先可以证明 \(\m M\) 中没有连接 \(\m{T_X}\) 和 \(\m{S_Y}\) 的边,这个东西的证明和之前的过程大致相同,只需要注意 \(\m{S_Y}\) 通过某条 \(\m M\) 中的边一定会把连接到的点生成到 \(\m{S_X}\) 里面去即可

然后我们只需要证明 \(\m M\) 中的每条边恰好连接 \(\m{S_Y}\cup\m{T_X}\) 中的一个顶点,证明和上面的依然类似,考虑 \(\m{S_Y},\m{T_X}\) 的生成过程:

  • 由于图上没有增广路,所以 \(\m{S_Y}\) 一定会和某个 \(\m{S_X}\) 中的点通过 \(\m M\) 中的边相连
  • 由于 \(\m{T_X}\) 里的点才不会在初始状态下被加入 \(\m{S_X}\),因此每个 \(\m{T_X}\) 中的点必须有一条 \(\m{M}\) 中的边连接

综上所述,我们能够得到一个大小为 \(|\m M|\) 的覆盖集 \(\m {T_X}\cup\m{S_Y}\)

联立两式有 \(|\m{VC}|=|\m M|\),故原命题得证

事实上,以上的证明过程告诉了我们如何求解一个二分图 \(\m G\) 的某个最小覆盖集和最大独立集

五、最小路径覆盖

I. 定义

在 DAG \(\m G=(\m V,\m E)\) 上选择若干条简单路径构成的集合 \(\m P\),使得 \(\m V\) 中的每一个顶点都恰好在 \(\m P\) 中的一条简单路径上

则 \(\m P\) 为 \(\m G\) 的一个路径覆盖

注意:这里的简单路径的长度可能为 \(0\)

II. 求解

构造一个二分图 \(\m G'=({\m X,\m Y,\m {E'}})\),其中 \(\m{E'}=\left\{(x_i,y_j)\mid(i,j)\in\m E\right\}\)

此时对于一个 \(\m{G'}\) 的匹配 \(\m M\),我们将所有 \(\m M\) 中的边 \(e'\) 对应回 \(\m G\) 上,得到一个路径集合 \(\m P\)

因为 \(\m M\) 是一个 \(\m {G'}\) 的匹配,我们可以发现 \(\m P\) 中没有两条边有相同的起点或终点,每个点至多各有一条边以其为起点和终点,那么这两条边可以连成一条路径

综上,我们得到一个 \(\m{G'}\) 中的匹配和 \(\m G\) 中的路径覆盖的双射,且满足 \(|\m P|=|\m V|-|\m M|\)

如果我们记 \(\m G\) 中的最小路径覆盖的大小为 \(|\m P|\),\(\m{G'}\) 中的最大匹配大小为 \(|\m M|\),那么我们有 \(|\m P|=|\m V|-|\m M|\)

六、习题演练

I. [CF Gym 101915D] - Largest Group

\(\text{Link}\)

思路分析

对每个男生认识的点状压,然后枚举每个男生构成的集合,取他们认识的女生集合的交,然后更新答案即可

时间复杂度 \(\Theta(p2^p)\)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=20;
int f[MAXN];
inline int calc(int S) { return __builtin_popcount(S); }
inline int bit(int k) { return 1<<k; }
inline void solve() {
	int n,m,ret=0;
	memset(f,0,sizeof(f));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i) {
		int u,v;
		scanf("%d%d",&u,&v);
		--u,--v;
		f[u]|=bit(v);
	}
	for(int S=1;S<bit(n);++S) {
		int T=bit(n)-1;
		for(int i=0;i<n;++i) if(S&bit(i)) T&=f[i];
		ret=max(ret,calc(S)+calc(T));
	}
	printf("%d\n",ret);
}
signed main() {
	int T;
	scanf("%d",&T);
	while(T--) solve();
	return 0;
}

II. [ACWing380] - 骑士放置

\(\text{Link}\)

思路分析

对棋盘进行黑白染色,注意到一个骑士能够攻击到的所有格子都和其本身所在格子异色,故将两个能互相攻击的格子连边,构造二分图

原问题转化为求二分图上的最大独立集,求出最大匹配即可

时间复杂度 \(\Theta(n^2m^2)\)

代码呈现

#include<bits/stdc++.h> 
using namespace std;
const int MAXN=101,MAXS=1e4+1;
bool a[MAXN][MAXN],vis[MAXS];
int n,m,tar[MAXS];
vector <int> edge[MAXS];
int dx[]={1,1,-1,-1,2,2,-2,-2};
int dy[]={2,-2,2,-2,1,-1,1,-1};
inline int id(int x,int y) {
	if(x<1||x>n||y<1||y>m||a[x][y]) return -1;
	return (x-1)*m+y;
}
inline bool match(int p) {
	for(int x:edge[p]) {
		if(vis[x]) continue;
		vis[x]=true;
		if(tar[x]==-1||match(tar[x])) {
			tar[x]=p;
			return true;
		}
	}
	return false;
}
signed main() {
	int t,ans=0;
	scanf("%d%d%d",&n,&m,&t);
	for(int i=1;i<=t;++i) {
		int x,y;
		scanf("%d%d",&x,&y);
		a[x][y]=true;
	}
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=m;++j) {
			if((i+j)%2==1||id(i,j)==-1) continue;
			for(int k=0;k<8;++k) {
				int x=i+dx[k],y=j+dy[k];
				if(id(x,y)==-1) continue;
				edge[id(i,j)].push_back(id(x,y));
			}
		}
	}
	memset(tar,-1,sizeof(tar));
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=m;++j) {
			if((i+j)%2==1||id(i,j)==-1) continue;
			memset(vis,false,sizeof(vis));
			if(match(id(i,j))) ++ans;
		}
	}
	printf("%d\n",n*m-t-ans);
	return 0;
}

III. [POJ1422] - Air Raid

\(\text{Link}\)

思路分析

最小路径覆盖的模板题,跑一边二分图最大匹配即可

时间复杂度 \(\Theta(nm)\)

代码呈现

#include<stdio.h>
#include<vector>
#include<string.h> 
using namespace std;
const int MAXN=121;
vector <int> edge[MAXN];
bool vis[MAXN];
int tar[MAXN];
inline bool match(int p) {
	for(int i=0;i<(int)(edge[p].size());++i) {
		int x=edge[p][i];
		if(vis[x]) continue;
		vis[x]=true;
		if(tar[x]==-1||match(tar[x])) {
			tar[x]=p;
			return true;
		}
	}
	return false;
}
inline void solve() {
	int n,m;
	scanf("%d%d",&n,&m);
	memset(tar,-1,sizeof(tar));
	for(int i=1;i<=n;++i) edge[i].clear();
	for(int i=1;i<=m;++i) {
		int u,v;
		scanf("%d%d",&u,&v);
		edge[u].push_back(v);
	}
	int ret=n;
	for(int i=1;i<=n;++i) {
		memset(vis,false,sizeof(vis));
		if(match(i)) --ret;
	}
	printf("%d\n",ret);
	return ;
}
signed main() {
	int T;
	scanf("%d",&T);
	while(T--) solve();
	return 0;
}

IV. [POJ1548] - Robots

\(\text{Link}\)

思路分析

考虑对于两个点 \(p_i:(x_i,y_i)\) 和 \(p_j:(x_j,y_j)\),如果可以从 \(p_i\) 走到 \(p_j\),即 \(x_i\le x_j,y_i\le y_j\),那么就在图 \(\m G\) 上连一条有向边 \(i\to j\),然后求出 \(\m G\) 的最小路径覆盖即可

设 \(n\) 为点的数量,时间复杂度 \(\Theta(n^3)\)

代码呈现

#include<stdio.h>
#include<vector>
#include<string.h>
using namespace std;
const int MAXN=1001;
int x[MAXN],y[MAXN],tar[MAXN],n;
vector <int> edge[MAXN];
bool vis[MAXN];
inline bool match(int p) {
	for(int i=0;i<(int)(edge[p].size());++i) {
		int x=edge[p][i];
		if(vis[x]) continue;
		vis[x]=true;
		if(tar[x]==-1||match(tar[x])) {
			tar[x]=p;
			return true;
		}
	}
	return false;
}
inline void solve() {
	for(int i=1;i<=n;++i) edge[i].clear();
	int ret=n;
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n;++j) {
			if(i!=j&&x[i]<=x[j]&&y[i]<=y[j]) {
				edge[i].push_back(j);
			}
		}
	}
	memset(tar,-1,sizeof(tar));
	for(int i=1;i<=n;++i) {
		memset(vis,false,sizeof(vis));
		if(match(i)) --ret;
	}
	printf("%d\n",ret);
}
signed main() {
	int a,b;
	n=0;
	while(true) {
		scanf("%d%d",&a,&b);
		if(a==-1&&b==-1) break;
		else if(a==0&&b==0) {
			solve();
			n=0;
		} else {
			++n;
			x[n]=a,y[n]=b;
		}
	}
	return 0;
}

V. [POJ3216] - Repairing Company

\(\text{Link}\)

思路分析

考虑建立图 \(\m{G'}=(\m{V'},\m{E'})\),\(\m {V'}\) 是所有的任务

对于每一个修理任务建成一个点 \(i\to j\) 当且仅当第 \(i\) 个任务完成后可以去完成 \(j\)

考虑 Floyd 处理出全源最短路 \(\Delta_{i,j}\) 表示 \(i,j\) 之间的最短路

那么:\((i,j)\in \m E'\iff t_i+d_i+\Delta_{p_i,p_j}\le p_j\)

因此建出 \(\m{G'}\) 后求出其最小路径覆盖即可得到答案

时间复杂度 \(\Theta(n^3+m^3)\)

代码呈现

#include<stdio.h>
#include<vector>
#include<string.h>
using namespace std;
const int MAXM=201,MAXN=21,INF=1e9;
int dis[MAXN][MAXN];
int p[MAXM],t[MAXM],d[MAXM],n,m;
vector <int> edge[MAXM];
bool vis[MAXM];
int tar[MAXM];
inline bool match(int p) {
	for(int i=0;i<(int)(edge[p].size());++i) {
		int x=edge[p][i];
		if(vis[x]) continue;
		vis[x]=true;
		if(tar[x]==-1||match(tar[x])) {
			tar[x]=p;
			return true;
		}
	}
	return false;
}
inline void solve() {
	for(int i=1;i<=m;++i) edge[i].clear();
	memset(tar,-1,sizeof(tar));
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n;++j) {
			scanf("%d",&dis[i][j]);
			if(dis[i][j]==-1) dis[i][j]=INF;
		}
	}
	for(int k=1;k<=n;++k) {
		for(int i=1;i<=n;++i) {
			for(int j=1;j<=n;++j) {
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
			}
		}
	}
	for(int i=1;i<=m;++i) scanf("%d%d%d",&p[i],&t[i],&d[i]);
	for(int i=1;i<=m;++i) {
		for(int j=1;j<=m;++j) {
			if(t[i]+d[i]+dis[p[i]][p[j]]<=t[j]) edge[i].push_back(j);
		}
	}
	int ret=m;
	for(int i=1;i<=m;++i) {
		memset(vis,false,sizeof(vis));
		if(match(i)) --ret;
	}
	printf("%d\n",ret);
} 
signed main() {
	while(true) {
		scanf("%d%d",&n,&m);
		if(n==0&&m==0) break;
		solve();
	}
	return 0;
}

VI. [POJ2594] - Treasure Exploration

\(\text{Link}\)

思路分析

求可相交路径最小覆盖问题,考虑转化为普通的最小路径覆盖问题

考虑建立图 \(\m{G'}=(\m V,\m{E'})\),其中 \((i,j)\in \m{E'}\) 等价于在原图中可以从 \(i\) 走到 \(j\)

此时 \(\m {G'}\) 中的一条边 \(u\to v\) 也对应 \(\m G\) 中的一条 \(u\to v\) 的路径,可以证明 \(\m{G'}\) 中的一个普通最小路径覆盖 \(\m{P'}\) 和 \(\m G\) 中的一个可相交路径最小覆盖 \(\m P\) 是一一对应的,证明如下:

证:

注意到 \(\m{G'}\) 中的一个普通路径覆盖一定覆盖了 \(\m V\),所以每个 \(\m {P'}\) 在 \(\m G\) 上都会成为一个合法的 \(\m P\)

对于一个 \(\m G\) 中的可相交路径最小覆盖,可以考虑成若干条路径 \(p_{u_i}\to p_{v_i}\),那么显然每条这样的路径都可以在 \(\m {G'}\) 中找到一条边 \(p_{u_i}\to p_{v_i}\) 与之对应,注意到因为 \(p_{u_i}\to p_{v_i}\) 是一条独立的路径,所以不存在 \(j\) 使得 \(p_{v_j}=p_{u_i}\) 或者 \(p_{v_i}=p_{u_j}\),因此这样的路径对应到 \(\m{P'}\) 上一定是一个普通路径路径覆盖的一部分,因为不存在另外一条 \(\m{P'}\) 中的边与 \(p_{u_i}\) 或者 \(p_{v_i}\) 相交

综上所述,我们证明了 \(\m{P}\) 和 \(\m {P'}\) 之间存在双射关系

此时我们只需要在 \(\m{G'}\) 中求出最小路径覆盖的的大小即可,注意到 \(\m{G'}\) 的建立事实上只要用 Floyd 传递闭包,因此最终的时间复杂度是 \(\Theta(n^3)\)

代码呈现

#include<stdio.h>
#include<string.h>
const int MAXN=501;
bool link[MAXN][MAXN],vis[MAXN];
int tar[MAXN],n,m;
inline bool match(int p) {
	for(int x=1;x<=n;++x) {
		if(!link[p][x]||x==p||vis[x]) continue;
		vis[x]=true;
		if(tar[x]==-1||match(tar[x])) {
			tar[x]=p;
			return true;
		}
	}
	return false;
}
inline void solve() {
	memset(link,false,sizeof(link));
	memset(tar,-1,sizeof(tar));
	for(int i=1;i<=n;++i) link[i][i]=true;
	for(int i=1;i<=m;++i) {
		int u,v;
		scanf("%d%d",&u,&v);
		link[u][v]=true;
	}
	for(int k=1;k<=n;++k) {
		for(int i=1;i<=n;++i) {
			for(int j=1;j<=n;++j) {
				link[i][j]|=link[i][k]&link[k][j];
			}
		}
	}
	int ret=n;
	for(int i=1;i<=n;++i) {
		memset(vis,false,sizeof(vis));
		if(match(i)) --ret;
	}
	printf("%d\n",ret);
}
signed main() {
	while(true) {
		scanf("%d%d",&n,&m);
		if(n==0&&m==0) break;
		solve();
	}
	return 0;
}

VII. 扑克牌(证明题)

命题

将一副扑克牌分成 \(13\) 堆,每一堆中恰好 \(4\) 张牌

求证:我们总是可以从每堆里面选出一张来使得选出的排构成 \(\texttt A,\texttt2,\texttt3,\cdots,\texttt{10},\texttt J,\texttt Q,\texttt K\)

证明

考虑将这些堆编号为 \(H_1\sim H_{13}\),扑克牌数字记为 \(1\sim 13\)

构造一张二分图 \(\m{G}=(\m X,\m Y,\m E)\),其中 \(\m X=\{H_1,H_2,\cdots,H_{13}\},\m Y=\{1,2,\cdots 13\}\), \((x_i,y_j)\in \m E\iff j\in H_i\)

即每个堆向其有的所有数字连边,此时我们即证明 \(\m G\) 中有大小为 \(|\m X|\) 的匹配

注意到对于 \(k\) 个数字,至少需要 \(k\) 个牌堆才能装下这些牌,即 \(\forall \m A\in\m Y:|N(\m A)|\le |\m A|\),运用 Hall 定理可以证明 \(\m G\) 中存在完美匹配

故原命题得证


VIII. Periodic Scheduling(证明题)

命题

假设有 \(n\) 种任务,你每天至多可以执行一种任务(也可以不执行)

第 \(i\) 种任务有一个参数 \(t_i\), 表示 \(\forall x\in \Z^+\),在区间 \((x\times t_i, (x+1)\times t_i]\) 这些天你必须执行任务 \(i\) 至少一次

令 \(L = \operatorname{lcm}(t_1,...,t_n)\),求证:存在一个可行的任务安排方案,当且仅当 \(\sum_{i=1}^n \dfrac L{t_i}\le L\)

证明

构造二分图 \(\m G=(\m X,\m Y,\m E)\)

\(\m X\) 表示每个任务的每个执行区间,即 \(x_{i,j}\) 表示 \((j\times t_i,(j+1)\times t_i]\),\(\m Y\) 表示第 \(1\sim L\) 的日期 \(y_1\sim y_L\)

\((x_{i,j},y_k)\in\m E\iff k\in(j\times t_i,(j+1)\times t_i]\) ,即向其需要的区间连边

此时一种合法的任务安排方案等价于 \(\m G\) 中有一个大小为 \(|\m X|\) 的匹配

运用 Hall 定理得到其一个必要条件是 \(|\m X|\le|\m Y|\) 即 \(\sum_{i=1}^n \dfrac L{t_i}\le L\),故必要性得证

下证充分性:

运用 Hall 定理,原命题等价为 \(|\m X|\le |\m Y|\implies\forall\m A\subseteq \m X:|N(\m A)|\ge|\m A|\)

考虑构造函数 \(f(\m A)=|N(\m A)|-|\m A|\),原命题再化为 \(f(\m X)\ge 0\implies \forall \m A\subseteq \m X:f(\m A)\ge 0\)

再转化成 \(\forall \m A\subseteq\m X,f(\m A)\ge f(\m X)\)

采用数学归纳法,令任务数为 \(k\),对于 \(k=1\) 时显然成立

假设对于 \(k=n-1\) 时成立,令 \(\m X_n=\{x_{n,1},x_{n,2},\cdots,x_{n,\frac{L}{t_n}}\}\),即所有对应任务 \(n\) 的左部点构成的集合

那么我们假设此时 \(\m A\cup\m X_n=\m{A}_n\),而 \(\overline{\m X_n}=\m{X'},\overline{\m A_n}=\m{A'}\)

根据归纳法假设, \(\forall \m{A'}:f(\m A')\ge f(\m {X'})\)

注意到 \(f(\m X)= f(\m{X'})-\dfrac L{t_n}=f(\m{X'})-|\m X_n|\),\(f(\m A)\ge f(\m A')-|\m A_n|\)

因此 \(f(\m A)-f(\m X)=(f(\m {A'})-f(\m{X'}))+(|\m X_n|-|\m A_n|)\ge 0\),故此时对于 \(k=n\) 也成立

事实上,当且仅当 \(\m A=\m X\) 的时候才有 $f(\m A)=f(\m X ) $

综上所述,原命题得证

标签:二分,VC,匹配,int,路径,MAXN
From: https://www.cnblogs.com/DaiRuiChen007/p/17026182.html

相关文章

  • 二分图(三)
    二分图(三)\(\newcommand\m\mathbf\)\(\text{ByDaiRuiChen007}\)一、Dilworth定理I.相关定义对于一个集合\(\mS\),定义\(\mS\)上的一种偏序关系\(\succsim\)......
  • C++ 数学与算法系列之牛顿、二分迭代法求解非线性方程
    1.前言前文介绍了如何使用“高斯消元法”求解线性方程组。本文秉承有始有终的态度,继续介绍“非线性方程”的求解算法。本文将介绍2个非线性方程算法:牛顿迭代法。二......
  • CodeForces 991C Candies(二分答案)
    CodeForces991CCandies(二分答案)http://codeforces.com/problemset/problem/991/C    题意:给出一个数n,表示有n块蛋糕,有两个人a,b。a每次可以取k块蛋糕(如果剩下......
  • Java二分法
    二分查找题目输入一个 n 个元素升序的整型数组 nums,再输入一个目标值 target 。编写一个方法:使用二分法,查找 nums 中的target,如果target存在,则返回在数组......
  • 排序+二分
    排序+二分排序快速排序基于分治思想确定分界点:\(q[l]\)\(q[l+r>>1]\)\(q[r]\) 随机快速排序这道题目的数据已加强,划分中点取左端点或右端点时会超时,改成取中......
  • 数据结构 玩转数据结构 7-7 基于二分搜索树的映射实现
    0课程地址https://coding.imooc.com/lesson/207.html#mid=13709 1重点关注1.1使用二叉树实现映射Map详见3.1用二叉树实现映射Map  2......
  • 02二分
    二分问题适用于一个序列,有一个check函数,能够使得序列左边都返回false,右边都返回true,然后我们找的就是这个分界点的时候基本思想两种情况一种是求左半段最后一个元素,......
  • 二分查找
    概念:适用范围:有序数组在顺序查找的基础上改进,每次查找都是用mid时间\(O(logn)\)算法intl=0,r=n-1;while(l<=r){//这里因为用来等号下面就要用减或者加int......
  • 二分学习笔记
    写在前面:本文中的“单调”不包括“单调不变”。(我不说你们应该也不会想到)一、算法引入如果我们要用一个数列(各个位置要有相应的数字形式的下标,且我们的这个下标可为小数......
  • leetcode笔记——二分图
    785.判断二分图-力扣(LeetCode)二分图实际上就是这个图里所有的环都是偶数个边,一般采取染色法来做通过dfs判断每个节点与其邻居节点是否是同一种颜色,如果有的话,那就一定......