ybtoj Au 二分图匹配

前言中的前言

  1. 由于本人过菜,有些题解会咕掉,请原谅这个蒟蒻

  2. 由于本人过菜,不知道什么时候就 AFO 了,想给这个机房留下点什么……

  3. 如果想看高效进阶的题解,建议出门左拐,去云落那里看看,保证是全网最全最好的,但不要对云落的博客好奇,更不要看云落的一言 和 云落的合集:黑夜刀己,白日爱人

正片开始!

好耶!!!(泪目)

题面

题目传送门

前言

二分图 T1,板子题

正文

求一个最小点覆盖,等于最大匹配数

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+6;
int n,k,head[N],cnt,p[N];
struct node{int to,nxt;}e[N];
void add(int u,int v){
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
bool vis[N];
bool match(int x){
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(!vis[y]){
			vis[y]=1;
			if(p[y]==0||match(p[y])){
				p[y]=x;
				return 1;
			}
		}
	}
	return 0;
}
int main(){
	cin>>n>>k;
	for(int i=1;i<=k;i++){
		int x,y;
		cin>>x>>y;
		add(x,y+n),add(y+n,x);
	}
	int cnt=0;
	for(int i=1;i<=n;i++){
		memset(vis,0,sizeof(vis));
		if(match(i))cnt++;
	}
	cout<<cnt;
	return 0;
}

后记

秒力

题面

题目传送门

前言

二分图 T2,应用题

正文

首先,黑白染色,那么马可以攻击到的格子是与他颜色不一样的

那么我们把马能到的点都连上边,此时就是要求二分图的最大独立集

答案即为点数-最大匹配数

代码

#include <bits/stdc++.h>
using namespace std;
const int N=205;
const int dx[9]={0,-1,-2,1,2,-1,-2,1,2};
const int dy[9]={0,-2,-1,-2,-1,2,1,2,1};
int n,cnt,head[N*N*2],cnt1;
bool vis[N*N*2];
struct node{int to,nxt;}e[N*N*2];
char s[N][N];
void add(int u,int v){
	e[++cnt1].to=v;
	e[cnt1].nxt=head[u];
	head[u]=cnt1;
}
int ans,p[N*N*2];
bool dfs(int x){
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].to;
		if(vis[v])continue;
		vis[v]=1;
		if(!p[v]||dfs(p[v])){
			p[v]=x;
			return 1;
		}
	}
	return 0;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>(s[i]+1);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(s[i][j]=='1'){
				cnt++;
				continue;		
			}
			for(int k=1;k<=8;k++){
				int x=i+dx[k],y=j+dy[k];
				if(s[x][y]=='1')continue;
				if(x>=1&&x<=n&&y>=1&&y<=n&&(i+j)%2)add((i-1)*n+j,(x-1)*n+y);
			}
		}
	}
	for(int i=1;i<=n*n;i++){
		memset(vis,0,sizeof(vis));
		if(dfs(i))ans++;
	}
	cout<<n*n-cnt-ans;
	return 0;
}

后记

重温了一下自己以前写的文(好尬),当然,现在的我写的文在将来的我看来也很尬吧

题面

题目传送门

前言

二分图 T3,应用题

正文

A 完成后能到达 B 那么连边

代码

#include <bits/stdc++.h>
using namespace std;
const int N=505;
struct node{int a,b,c,d,t;}p[N];
int n,h,m,cnt,head[N*N],o[N];
struct Node{int to,nxt;}e[N*N];
void add(int u,int v){
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
char c;
int dis(int i,int j){
	return abs(p[i].c-p[j].a)+abs(p[i].d-p[j].b);
}
bool vis[N];
bool dfs(int x){
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(vis[y])continue;
		vis[y]=1;
		if(!o[y]||dfs(o[y])){
			o[y]=x;
			return 1;
		}
	}
	return 0;
}
int main(){
	int t;
	cin>>t;
	while(t--){
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>h>>c>>m>>p[i].a>>p[i].b>>p[i].c>>p[i].d;
			p[i].t=h*60+m;
			//cout<<p[i].t<<endl;
		}	
		cnt=0;
		memset(head,0,sizeof(head));
		for(int i=1;i<=n;i++){
			for(int j=i+1;j<=n;j++){
				if(p[i].t+dis(i,i)+dis(i,j)<p[j].t){
					add(i,j);
					//cout<<i<<" "<<j<<endl;
				}
			}
		}
		int ans=0;
		memset(o,0,sizeof(o));
		for(int i=1;i<=n;i++){
			memset(vis,0,sizeof(vis));
			if(dfs(i))ans++;
		}
		cout<<n-ans<<endl;
	}
	return 0;
}

后记

博客没保存……

题面

题目传送门

前言

二分图 T4,应用题

正文

结论:在这个博弈中,如果初始在二分图的所有最大匹配上,那么先手必胜,反之后手必胜。

其中加粗的点为在任意最大匹配上的点。不妨先称为“粗点”,相对地有“细点”。

可以发现,从一个粗点出发,只需要任意选择一个在任意一个最大匹配上的匹配点。

只要先手走的是最大匹配,无论后手怎么走,因为题目要求不能回头,所以一定可以继续沿着一个最大匹配走。

梳理一下这是怎么回事:只要先手在粗点,那么无论对方怎么走,永远可以再次走最大匹配。如果不可以,这就与最大匹配的“最大”矛盾了。

反之,如果在细点上,那么后手一定可以在一个粗点上出发,即后手必胜。

所以,题目所求的就转化为:求在一个二分图上,有哪些点不一定在最大匹配上。

只要求出这个细点集,空(也就是说,存在完美匹配,任意点皆为粗点)即输出 LOSE,非空输出 WIN 即可。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=200005;
const int dx[5]={0,-1,0,1,0};
const int dy[5]={0,0,1,0,-1};
int n,m,x[N],y[N],X,Y,head[N],cnt;
char s[205][205];
struct node{int to,nxt;}e[N];
void add(int u,int v){
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
bool flag,vis[N],mark[N];
int p[N];
bool dfs(int x){
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(!vis[y]){
			vis[y]=1;
			if(dfs(p[y])||!p[y]){
				p[y]=x;
				return 1;
			}
		}
	}
	return 0;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>(s[i]+1);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(s[i][j]=='.'){
				if((i+j)%2){
					x[++X]=i*m+j;
					for(int k=1;k<=4;k++){
						int xx=i+dx[k],yy=j+dy[k];
						if(s[xx][yy]=='.'){
							add(i*m+j,xx*m+yy);
							add(xx*m+yy,i*m+j);
						}
					}
				}else{
					y[++Y]=i*m+j;
				}
			}
		}
	}
	for(int i=1;i<=X;i++){
		memset(vis,0,sizeof(vis));
		if(!dfs(x[i])){
			flag=1;
			mark[x[i]]=1;
		}
	}
	for(int i=1;i<=Y;i++){
		if(!p[y[i]]){
			mark[y[i]]=1;
			flag=1;
		}else{
			memset(vis,0,sizeof(vis));
			vis[y[i]]=1;
			if(dfs(p[y[i]])){
				p[y[i]]=0;
				mark[y[i]]=1;
				flag=1;
			}
		}
	}
	for(int i=1;i<=Y;i++){
		memset(vis,0,sizeof(vis));
		if(!dfs(y[i])){
			flag=1;
			mark[y[i]]=1;
		}
	}
	for(int i=1;i<=X;i++){
		if(!p[x[i]]){
			mark[x[i]]=1;
			flag=1;
		}else{
			memset(vis,0,sizeof(vis));
			vis[x[i]]=1;
			if(dfs(p[x[i]])){
				p[x[i]]=0;
				mark[x[i]]=1;
				flag=1;
			}
		}
	}
	if(!flag){cout<<"LOSE"<<endl;return 0;}
	cout<<"WIN"<<endl;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(mark[i*m+j]){
				cout<<i<<" "<<j<<endl;
			}
		}
	}
	return 0;
}

后记

好难好难

题面

题目传送门

前言

二分图 T5,水题

正文

把原来有 “” 的拆开

把能合并的连边

代码

#include <bits/stdc++.h>
using namespace std;
const int N=2e6+6;
int n,m,ans,op,head[N],cnt,p[N];
bool flag[N],vis[N];
char s[N];
struct node{int to,nxt;}e[N];
void add(int u,int v){
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
bool dfs(int x){
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(vis[y])continue;
		vis[y]=1;
		if(!p[y]||dfs(p[y])){
			p[y]=x;
			return 1;
		}
	}
	return 0;
}
int main(){
	while(cin>>n>>m,n){
		memset(flag,0,sizeof(flag));
		memset(head,0,sizeof(head));
		memset(p,0,sizeof(p));
		op=ans=cnt=0;
		for(int i=1;i<=m;i++){
			cin>>s;
			int tmp=0,pos=-1;
			for(int j=0;j<n;j++){
				if(s[j]=='1')tmp+=1<<j;
				else if(s[j]=='*')pos=j;
			}	
			flag[tmp]=1;
			if(~pos)flag[tmp+(1<<pos)]=1;
		}
		for(int i=0;i<(1<<n);i++){
			if(flag[i]){
				op++;
				for(int j=0;j<n;j++){
					if(flag[i^(1<<j)])add(i,i^(1<<j));
				}
			}
		}
		for(int i=0;i<(1<<n);i++){
			memset(vis,0,sizeof(vis));
			if(dfs(i))ans++;
		}
		cout<<op-ans/2<<endl;
	}
	return 0;
}

后记

洛谷会 T,不理解

题面

题目传送门

前言

二分图 T6,应用题

正文

求最大独立集

(最长反链:在反链上 x 不能到达 yy 也不能到达 x

代码

#include <bits/stdc++.h>
using namespace std;
int n,m,f[205][205],ans,head[40005],cnt,p[40005];
bool vis[40005];
struct node{int to,nxt;}e[40005];
void add(int u,int v){
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
bool dfs(int x){
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(vis[y])continue;
		vis[y]=1;
		if(!p[y]||dfs(p[y])){
			p[y]=x;
			return 1;
		}
	}
	return 0;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		f[u][v]=1;
	}
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				f[i][j]|=f[i][k]&f[k][j];
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(f[i][j]){
				add(i,j+n),add(j+n,i);
			}
		}
	}
	for(int i=1;i<=n;i++){
		memset(vis,0,sizeof(vis));
		if(dfs(i))ans++;
	}
	cout<<n-ans;
	return 0;
}

后记

去洛谷上水黑题吧(但要输出方案)

题面

题目传送门

前言

二分图 T7,应用题

正文

如果他是二维的,那么直接用第一题的思路就行

观察到 abc5000,反证法可以轻易地证出 a,b,c 中有一个 5000317.1

钦定是 a

这样就暴力枚举 1a 中的某一层是直接削掉还是一会儿再处理

代码

#include <bits/stdc++.h>
using namespace std;
int t,minn=0x7f7f7f7f;
const int N=5005;
int op[4][N],m,ans,cnt,opp,head[N],p[N],a,b,c;
struct node{int to,nxt;}e[N];
void add(int u,int v){
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
bool oi[N],vis[N];
bool dfs(int x){
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		if(vis[y])continue;
		vis[y]=1;
		if(!p[y]||dfs(p[y])){
			p[y]=x;
			return 1;
		}
	}
	return 0;
}
void check(int x){
	memset(head,0,sizeof(head));
	memset(p,0,sizeof(p));
	cnt=0,opp=0;
	for(int i=0;i<a;i++){
		if(x&(1<<i))oi[i+1]=0,opp++;
		else oi[i+1]=1;
	}
	for(int i=1;i<=m;i++){
		if(oi[op[1][i]])add(op[2][i],op[3][i]);
	}
	for(int i=1;i<=b;i++){
		memset(vis,0,sizeof(vis));
		if(dfs(i))opp++;
	}
	ans=min(opp,ans);
}
int main(){
	cin>>t;
	while(t--){
		m=0;
		ans=0x7f7f7f7f;
		cin>>a>>b>>c;
		minn=min(a,min(b,c));
		for(int i=1;i<=a;i++){
			for(int j=1;j<=b;j++){
				for(int k=1;k<=c;k++){
					int o;
					cin>>o;
					if(!o)continue;
					op[1][++m]=i;
					op[2][m]=j;
					op[3][m]=k;
				}
			}
		}
		if(minn==b)swap(a,b),swap(op[1],op[2]);
		else if(minn==c)swap(a,c),swap(op[1],op[3]);
		for(int i=0;i<(1<<a);i++){
			check(i);
		}
		cout<<ans<<endl;
	}
	return 0;
}

后记

好难好难

posted @   小惰惰  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
/* 鼠标点击求赞文字特效 */
点击右上角即可分享
微信分享提示

目录导航