[二分图] 专题:二分图判定与匹配

文章目录

  • P1 [关押罪犯](https://www.luogu.com.cn/problem/P1525)
  • P2 二分图匹配
  • P3 其它问题
  • P4 二分图带权匹配
  • P5 练习

P1 关押罪犯

二分答案
染色法二分图判定,其实就DFS
建图:怨气值>mid的连边

注意开始DFS的时候不能DFS(1,0),因为0没有col

#include
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar();
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=1e5+5;
int n,m,col[N];
struct Data{
	int p1,p2,val;
}a[N];
vector<int>G[N];
bool flag;

bool cmp(Data a,Data b){
	return a.val<b.val;
}

void DFS(int u,int c){
	if(!flag) return;
	col[u]=c;
	for(int e=0;e<G[u].size();++e){
		int v=G[u][e];
		if(!col[v])
			DFS(v,-c);
		else if(col[v]==c)
			flag=false;
	}
	return;
}

bool check(int pos){
	for(int i=1;i<=n;++i) G[i].clear();
	for(int i=pos+1;i<=m;++i){
		G[a[i].p1].push_back(a[i].p2);
		G[a[i].p2].push_back(a[i].p1);
	}
	flag=true;
	memset(col,0,sizeof(col));
	for(int i=1;i<=n;++i)
		if(!col[i]){
			DFS(i,1);
			if(!flag) return false;
		}
	return true;
}

int main(){
	n=in,m=in;
	for(int i=1;i<=m;++i){
		a[i].p1=in;
		a[i].p2=in;
		a[i].val=in;
	}
	if(m==1){
		puts("0");
		return 0;
	}
	sort(a+1,a+m+1,cmp);
	int l=0,r=m,ans;
	while(l<=r){
		int mid=l+r>>1;
		if(check(mid)) ans=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%d",a[ans].val);
	return 0;
}

P2 二分图匹配

增广路的概念:

存在一条连接两个非匹配点的路径path,使得使得非匹配边与匹配边在path上交替出现,那么称path为增广路

注意,这里的一条指的不是一条边,而是多条边组成的路径

发掘一下性质:

  • 对于一条增广路,只有头尾节点为非匹配点,内部的节点都是匹配点
  • 增广路内部由匹配边和非匹配边交替出现
  • 匹配边数量为 n n n,则非匹配边数量为 n + 1 n+1 n+1(内部交替出现和两头连接非匹配点)
  • 增广路长度为奇数
  • 增广路中非匹配边也是一个匹配

由最后一条性质,可以扩大匹配边
即匈牙利算法,贪心,后来居上

放一点例题,傻逼洛谷上找不到题我就不做了
最重要的是如何建图

棋盘覆盖
n × m n\times m n×m棋盘上放骨牌( 1 × 2 1\times 2 1×2),有些格子禁止放,求最多放多少骨牌
n , m ≤ 100 n,m\leq 100 n,m100

DP可做,自己YY一下转移方程
考虑二分图:
每个骨牌就是一条边,连接两个格子
考虑建图:行号列号和为奇数、偶数的分别为二分图的左部和右部
最大匹配
Θ ( N 2 M 2 ) \Theta(N^2M^2) Θ(N2M2)

车的放置
还是一个棋盘 n × m n\times m n×m,放象棋车,互不攻击
有些位置禁止放置
n , m ≤ 200 n,m\leq 200 n,m200

其实递推是可以的(可参见福建省选的那道题)
考虑行号和列号为二分图左部右部
二分图匹配
Θ ( ( N + M ) × N M ) \Theta((N+M)\times NM) Θ((N+M)×NM)

P3 其它问题

完备匹配 左部右部节点数相同,且在最大匹配中均为匹配点
求到最大匹配判断一下就行了

多重匹配 就是求多个匹配
直接逃去网络流

多重匹配会有最有匹配,那贪心,二分就可以上来了

P4 二分图带权匹配

二分图能解决的一切问题网络流都可以解决
二分图不能解决的一切问题网络流还是都可以解决

但是有例外
有专门卡网络流的题让你写KM

KM算法见我博客

P5 练习

矩阵游戏
行列式的性质!!! 当我没说
一个黑点即连一条边
交换行列即交换同部节点
最后得到 ( 1 , 1 ) , ( 2 , 2 ) , ⋯   , ( n , n ) (1,1),(2,2),\cdots,(n,n) (1,1),(2,2),,(n,n)的点就Yes
实际上交换同部节点不改变图的构成,因为这些节点只有个标号是起作用的
于是直接二分图匹配,匹配数 = n =n =nYes

注意建图,注意节点:
左部右部节点是有区别的!
如左部范围 [ 1 , n ] [1,n] [1,n],右部范围就不能是 [ 1 , n ] [1,n] [1,n],取 [ n + 1 , 2 n ] [n+1,2n] [n+1,2n]挺好

傻逼洛谷,印度女工又罢工了

#include
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar();
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=5e2+5;
int n,ans;
vector<int>G[N];
bool vis[N];
int mat[N];

bool DFS(int u){
	for(int e=0;e<G[u].size();++e){
		int v=G[u][e];
		if(!vis[v]){
			vis[v]=true;
			if(!mat[v]||DFS(mat[v])){
				mat[v]=u;
				return true;
			}
		}
	}
	return false;
}

int main(){
	int T=in;
	while(T--){
		n=in,ans=0;
		memset(mat,0,sizeof(mat));
		for(int i=1;i<=n;++i) G[i].clear();
		for(int i=1;i<=n;++i)
			for(int j=1;j<=n;++j){
				int x=in;
				if(x){
					G[i].push_back(n+j);
					G[n+j].push_back(i);
				}
			}
		for(int i=1;i<=n;++i){
			memset(vis,0,sizeof(vis));
			ans+=DFS(i);
		}
		if(ans==n) puts("Yes");
		else puts("No");
		
//		for(int i=1;i<=n;++i){
//			for(int e=0;e
//			puts("");
//		}
//		printf("ans=%d\n",ans);
//		for(int i=1;i<=n;++i) printf("%d ",mat[i]);puts("");
//		
	}
	return 0;
}

变换序列
发现距离的含义可以把 T i T_i Ti扔到 [ N + 1 , 2 N ] [N+1,2N] [N+1,2N]上去
标准的二分图匹配

考虑字典序的问题
由于匈牙利算法的替换操作,我们没办法保证前面拿到字典序小的节点后面能保持这个值
考虑倒着寻找匹配,后面的点如果拿到了字典序小的点最后会被挤成字典序大的点
至于为什么不能仍然正向寻找匹配,最后倒着输出呢?
感觉上,发现二分图不是对称的,没办法倒着来~~(好玄学)~~

#include
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar();
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=1e4+5;
int n,ans;
int G[2][N];
int vis[N],tim;
int mat[N],to[N];

bool DFS(int u){
	for(int i=0;i<2;++i){
		int v=G[i][u];
		if(vis[v]!=tim){
			vis[v]=tim;
			if(mat[v]==-1||DFS(mat[v])){
				mat[v]=u;
				to[u]=v;
				return true;
			}
		}
	}
	return false;
}

int main(){
	memset(mat,-1,sizeof(mat));
	n=in;
	for(int i=0;i<n;++i){
		int d=in;
		int a=i-d<0?i-d+n:i-d;
		int b=i+d>=n?i+d-n:i+d;
		if(a>b) swap(a,b);
		G[0][i]=a;
		G[1][i]=b;
	}
	for(int i=n-1;~i;--i)
		++tim,ans+=DFS(i);
	if(ans<n) puts("No Answer");
	else{
		for(int i=0;i<n;++i)
			printf("%d ",to[i]);
		puts("");
	}
	
	return 0;
}

[ZJOI2009]假期的宿舍
好板
懒得写了(今天下午状态不好)

你可能感兴趣的:([二分图] 专题:二分图判定与匹配)