Living-Dream 系列笔记 第89期

继续学习二分图最大匹配。

P10937

「行列匹配考虑建二分图。」

考虑将行视为左部点,列视为右部点,车视为边,则转化为一个最大匹配模型,即最多(车放置的数量最多)且不共点(每个车都不互相攻击)。做完了。

code
#include<bits/stdc++.h>
using namespace std;

const int N=2e2+5;
int n,m,t,id;
int vis[N];
bool no[N][N];
int match[N];
vector<int> G[N];

bool hungary(int cur){
	if(vis[cur]==id)
		return 0;
	vis[cur]=id;
	for(int i:G[cur]){
		if(match[i]==0||hungary(match[i])){
			match[i]=cur;
			return 1;
		}
	}
	return 0;
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m>>t;
	for(int i=1,u,v;i<=t;i++){
		cin>>u>>v;
		no[u][v]=1;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(no[i][j]==0)
				G[i].push_back(j);
	int ans=0;
	for(int i=1;i<=n;i++){
		++id;
		if(hungary(i))
			++ans;
	}
	cout<<ans;
	return 0;
}

P1129

仍然考虑上面的建图方式,那么交换操作就可以看作是将一堆缠绕着的「线」展开,最终达成 \(1 \to 1,2 \to 2,...,n \to n\)(即所有棋子都在主对角线上)的情形。如图。

image

image

于是乎,只要最大匹配数为 \(n\) 则有解,否则无解。

code
#include<bits/stdc++.h>
using namespace std;

const int N=2e2+5;
int n,m,t,id;
int vis[N];
int match[N];
vector<int> G[N];

bool hungary(int cur){
	if(vis[cur]==id)
		return 0;
	vis[cur]=id;
	for(int i:G[cur]){
		if(match[i]==0||hungary(match[i])){
			match[i]=cur;
			return 1;
		}
	}
	return 0;
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>t;
	while(t--){
		for(int i=1;i<=n;i++)
			G[i].clear();
		memset(match,0,sizeof match);
		memset(vis,0,sizeof vis);
		cin>>n;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				int c; cin>>c;
				if(c)
					G[i].push_back(j);
			}
		}
		int ans=0; id=0;
		for(int i=1;i<=n;i++){
			++id;
			if(hungary(i))
				++ans;
		}
		cout<<(ans==n?"Yes\n":"No\n");
	}
	return 0;
}

P2055

显然的,人向床连边。

具体的,在校且不回家的向自己和认识的人连,外校的向认识的人连,回家的不考虑。

然后就是如果最大匹配数为所有在校且不回家以及外校的人(注意不是 \(n\)!!!)就有解,否则无解。

code
#include<bits/stdc++.h>
using namespace std;

const int N=4e3+5;
int n,m,t,id;
int vis[N];
bool on[N],ok[N];
int match[N];
vector<int> G[N];

void init(){
	id=0;
	memset(on,0,sizeof on);
	memset(ok,0,sizeof ok);
	memset(match,0,sizeof match);
	memset(vis,0,sizeof vis);
	for(int i=1;i<=n;i++)
		G[i].clear();
}
bool hungary(int cur){
	if(vis[cur]==id)
		return 0;
	vis[cur]=id;
	for(int i:G[cur]){
		if(match[i]==0||hungary(match[i])){
			match[i]=cur;
			return 1;
		}
	}
	return 0;
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>t;
	while(t--){
		init();
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>on[i];
		}
		for(int i=1,f;i<=n;i++){
			cin>>f;
			if(on[i]&&f==0)
				ok[i]=1;
		}
		for(int i=1;i<=n;i++){
			for(int j=1,f;j<=n;j++){
				cin>>f;
				if(f&&on[j]&&(ok[i]||on[i]==0))
					G[i].push_back(j);
			}
		}
		for(int i=1;i<=n;i++)
			if(ok[i])
				G[i].push_back(i);
		int ans=0,all=0;
		for(int i=1;i<=n;i++){
			if(ok[i]||on[i]==0)
				all++;
			++id;
			if(hungary(i))
				++ans;
		}
		cout<<(ans==all?"^_^":"T_T")<<'\n';
	}
	return 0;
}

P2526

必经点和景点连边,前提是通过景点的路径长度不超过两个必经点之间距离的两倍。

跑匈牙利算法即可,然后答案就是所有必经点加上可能的景点,注意为了输出方便需要记一个反向的 match。

code
#include<bits/stdc++.h>
#define double long double
#define eps 1e-10
using namespace std;

const int N=1e2+5;
int n,m,id;
int vis[N],ml[N],mr[N];
pair<double,double> a[N],b[N];
vector<int> G[N];
vector<pair<double,double> > ans;

bool hungary(int cur){
	if(vis[cur]==id)
		return 0;
	vis[cur]=id;
	for(int i:G[cur]){
		if(ml[i]==0||hungary(ml[i])){
			ml[i]=cur,mr[cur]=i;
			return 1;
		}
	}
	return 0;
}
double g(double x,double y,double xx,double yy){
    return sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy));
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>a[i].first>>a[i].second;
	for(int i=1;i<=m;i++)
		cin>>b[i].first>>b[i].second;
	for(int i=1;i<n;i++){
		double d1=g(a[i].first,a[i].second,a[i+1].first,a[i+1].second);
		for(int j=1;j<=m;j++){
			double d2=g(a[i].first,a[i].second,b[j].first,b[j].second);
			double d3=g(a[i+1].first,a[i+1].second,b[j].first,b[j].second);
			if(d2+d3-2.0*d1<=eps)
				G[i].push_back(j);
		}
	}
	for(int i=1;i<n;i++)
		++id,hungary(i);
	for(int i=1;i<=n;i++){
	    ans.push_back({a[i].first,a[i].second});
		if(mr[i])
			ans.push_back({b[mr[i]].first,b[mr[i]].second});
	}
	cout<<ans.size()<<'\n';
	for(auto i:ans)
	    cout<<i.first<<' '<<i.second<<' ';
	return 0;
}

总结:

  • 如何看出二分图?题中有明显图论性质(类似于攻击、排斥等)且只有两个点集,抑或是行列这种经典模型。

  • 如何想到最大匹配?有一个满足要求最多且不共点的东西,它就是边/匹配。抓住两个最大匹配的关键性质即可。

  • 如何进一步思考?左部点 / 右部点 / 连边,往这三个方面想准没错。

  • 菜就多练。

posted @ 2024-12-07 23:36  _KidA  阅读(27)  评论(0)    收藏  举报