目录
学习篇
二分图以及其最大匹配
一、什么是二分图
如果一个图的顶点可以分为两个集合X、Y, 图的所有边一定是有一个顶点属于集合X,另一个顶点属于集合Y, 则称该图为“二分图”或“二部图”。
二、二分图的最大匹配
在二分图的应用中,最常见的就是求————最大匹配,很多其他问题都可以转化为匹配问题来解决。
匈牙利算法
举个例子:男女婚配(详看hdu 2063
遍历左边男的:
男1:遍历右边女的, 从 女1 开始,有连线并且本轮没用过,则男1 女1 匹配 为一对
男2:遍历右边女的,从 女1 开始,有连线并且本轮没用过,但女1 有对象,则看看 其对象是否有另外的选择,若有则 到目前为止一共匹配两对,若无则 只有一对
二分图最大匹配变形题
最小顶点覆盖 || 最大独立集
定义:用最少的顶点覆盖所有的边
最小顶点覆盖=匹配对数(详看hdu 1150)
最大独立集=总顶点数-匹配数
DAG图的最小路径覆盖
定义:用最少的路覆盖所有的点(详看hdu 1151
最小路覆盖=点的总数 - 最大匹配
因为:匹配一个,点就少一个
做法:X、Y集合均为相同的顶点,表示从X -> Y的顶点
水题集
hdu 2063:男女最大匹配
#include <bits/stdc++.h>
using namespace std;
int K,M,N,v[510],mp[510][510],p[510];
//mp[][]记录两顶点之间的连线,p用来保存女生的对象,v标记数组
bool dfs(int i){//dfs的含义是 判断该编号i的男生能否找到对象
for(int j=1;j<=N;j++){//遍历女生
if(mp[i][j]&&!v[j]){//若本轮有连线且没用过的女生,则标记
v[j]=1;
if(!p[j] || dfs(p[j])){//若该女生没有对象 或者 本来有对象但原来的对象能找到别的女生则可以让 i编号的男生为 j编号的女生的对象
p[j]=i;return 1;
}
}
}
return 0;//该男生没找到对象
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
while(cin>>K){
if(K==0)break;
cin>>M>>N;
int ans=0;
memset(mp,0,sizeof mp);
for(int i=1;i<=K;i++){
int g,b;cin>>g>>b;
mp[g][b]=1;
}
memset(p,0,sizeof(p));//将所有女生的对象初始化为一个值,且该值不等于任何男生的编号(-1或0)
for(int i=1;i<=M;i++){
memset(v,0,sizeof v);//每轮都要初始化
if(dfs(i))ans++;//找到对象,匹配数加加
}
cout<<ans<<endl;;
}
return 0;
}
hdu 1150:机器最少重启次数
题意:两个机器,初始模式均为0,变成下一个模式要重启,每个任务对于两种机器有各自的模式,求两种机器最少重启几次才能完成所有任务
本质:最少顶点覆盖,边的总数就是任务数,对应的两种机器的模式就是两个集合的点;
注意:机器本身就在0模式,所以要预处理一下:跟 0模式相连的点不需要 记录两点的连线
#include <bits/stdc++.h>
using namespace std;
int n,m,k,v[110],mp[110][110],p[110];
bool dfs(int i){
for(int j=1;j<m;j++){
if(mp[i][j] && !v[j]){
v[j]=1;
if(p[j]==-1 || dfs(p[j]) ){
p[j]=i;return 1;
}
}
}
return 0;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
while(cin>>n){
if(n==0)break;
cin>>m>>k;
memset(mp, 0,sizeof mp);
for(int i=0;i<k;i++){
int a,b;cin>>i>>a>>b;
if(a==0 ||b==0)continue;//预处理
mp[a][b]=1;
}
int ans=0;
memset(p,-1,sizeof p);//因为“对象”有编号为0的,所以初始化为-1
for(int i=1;i<n;i++){
memset(v,0,sizeof v);
if(dfs(i))ans++;
}
cout<<ans<<endl;
}
return 0;
}
hdu 1151:DAG最小覆盖路
题意:给路口的数量、路的数量及其连通的方式(两个路口编号)
本质:DAG图的最小覆盖路
#include <bits/stdc++.h>
using namespace std;
int n,m,p[130],mp[130][130],v[130];
bool dfs(int i){
for(int j=1;j<=n;j++){
if(mp[i][j]&&!v[j]){
v[j]=1;
if(!p[j] || dfs(p[j])){
p[j]=i;return 1;
}
}
}
return 0;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t;cin>>t;
while(t--){
cin>>n>>m;
memset(mp,0,sizeof mp);
for(int i=1;i<=m;i++){
int a,b;cin>>a>>b;
mp[a][b]=1;
}
int ans=0;
memset(p,0,sizeof p);
for(int i=1;i<=n;i++){
memset(v,0,sizeof v);
if(dfs(i))ans++;
}
cout<<n-ans<<endl;
}
return 0;
}
hdu 1068:最大没有联系的男女个数
本质:最大独立集 -> 二分图最大匹配
注意:本题并没有指明男女生的分类,那么两个集合采用的均为所有人的编号,最后结果就是:总人数-最大匹配数/2
#include <bits/stdc++.h>
using namespace std;
int n,ans,p[1000],v[1000],mp[1000][1000];
bool dfs(int i){
for(int j=0;j<n;j++){
if(mp[i][j]&&!v[j]){
v[j]=1;
if(p[j]==-1||dfs(p[j])){
p[j]=i;return 1;
}
}
}
return 0;
}
int main(){
while(scanf("%d",&n)==1){
ans=0;
memset(mp,0, sizeof mp);
for(int i=0;i<n;i++){
int k;scanf("%d: (%d)",&i,&k);
while(k--){
int a;scanf("%d",&a);
mp[i][a]=1;
}
}
memset(p,-1,sizeof p);
for(int i=0;i<n;i++){
memset(v,0,sizeof v);
if(dfs(i))ans++;
}
printf("%d\n",n-ans/2);
}
return 0;
}
hdu 1281 :骑士最大放置数
题意:在一个棋盘里,给出能放置棋子的格子数以及具体坐标,求骑士最多能多少个(任何棋子之间不能伤害对方);求重要点的数量,重要点:即不放该个格子,骑士最大值就变了
国际象棋中的骑士是横竖走的,两个棋子的坐标x互不相同,y坐标互不相同;即可以转化为集合X 和 集合Y 的最大匹配数
求重要点:依次将某个点删去(两点间的连线由1->0),在进行二分匹配操作,看匹配书是否改变
#include <bits/stdc++.h>
using namespace std;
int n,m,k,ans,p[110],v[110],mp[110][110];
bool dfs(int i){
for(int j=1;j<=m;j++){
if(mp[i][j]&&!v[j]){
v[j]=1;
if(!p[j] || dfs(p[j])){
p[j]=i;return 1;
}
}
}
return 0;
}
int match(){
int res=0;
memset(p,0,sizeof p);
for(int i=1;i<=n;i++){
memset(v,0,sizeof v);
if(dfs(i))res++;
}
return res;
}
int main(){
int t=1;
while(scanf("%d%d%d",&n,&m,&k)==3){
memset(mp,0,sizeof mp);
while(k--){
int x,y;scanf("%d%d",&x,&y);
mp[x][y]=1;
}
ans=match();
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(mp[i][j]){
mp[i][j]=0;
if(match()<ans)cnt++;
mp[i][j]=1;
}
}
}
printf("Board %d have %d important blanks for %d chessmen.\n",t++,cnt,ans);
}
return 0;
}