二分图(一)

\(\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. 算法流程

二分图(一)

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

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\) 维护此时某条交错轨的终点,朴素的算法流程如下:

二分图(一)

这样暴力找增广轨的复杂度 \(\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\lor 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 ) $

综上所述,原命题得证