$$ \newcommand{\seq}[2]{{#1}_{1},{#1}_{2},\cdots,{#1}_{#2}} \newcommand{\num}[1]{1,2,\cdots,#1} \newcommand{\stra}[2]{\begin{bmatrix}#1 \\ #2\end{bmatrix}} \newcommand{\strb}[2]{\begin{Bmatrix}#1 \\ #2\end{Bmatrix}} \newcommand{\dw}[1]{\underline{#1}} \newcommand{\up}[1]{\overline{#1}} $$

二分图匹配

概念

二分图

若一张图中所有顶点能涂成两种颜色,且相邻两点颜色不同,则该图为二分图。

匹配

若二分图 \(G\) 的子图 \(M\) 满足 \(M\) 的任意两条边都不连接同一个顶点,则称 \(M\)\(G\) 的一个匹配。

最大匹配

\(|M|\)最大的 \(M\)\(G\) 的最大匹配。

求最大匹配

匈牙利算法

\(n_1\) 为二分图左侧顶点数, \(n_2\) 为二分图左侧顶点数, \(m\) 为二分图边数,则复杂度 \(O(n_1n_2)\)

Code

#include<cstdio>
#define maxn 1002
#define maxm 1000002
using namespace std;
struct edge{
	int to,next;
};
edge e[maxm];
int head[maxn],cnte;
void add(int u,int v){
	e[++cnte].to=v;
	e[cnte].next=head[u];
	head[u]=cnte;
}
int c[maxn],n1,n2,m;
bool vis[maxn];
bool dfs(int u){
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(vis[v])continue;
		vis[v]=1;
		if(!c[v]||dfs(c[v])){
			c[v]=u;
			return 1;
		}
	}
	return 0;
}
int match(){
	int ret=0;
	for(int i=1;i<=n1;i++){
		for(int j=1;j<=n2;j++)vis[j]=0;
		if(dfs(i))ret++;
	}
	return ret;
}
int main(){
	scanf("%d%d%d",&n1,&n2,&m);
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v); //注意是建单向边
	}
	printf("%d\n",match());
	return 0;
}

例题

洛谷 P3386 【模板】二分图匹配

二分图匹配模板题。

POJ - 3041 Asteroids

题意

给你一个 \(n\)\(m\) 列的矩阵,矩阵中用若干个点,每次你可以消去一整行或一整列的点,求最少多少次能把所有点消完。

分析

我们设 \(u_i(i\in [1,n])\) (行号集合)为二分图左侧顶点, \(v_i(i\in [1,m])\) (列号集合)为二分图右侧顶点,对于矩阵中的每一个点 \((x,y)\) ,连边 \((x,y)\) ,这样,题目就转化成了求大小最小的顶点集合\(V\),使得所有边都依附于 \(V\) 中的顶点。这其实就是最小点覆盖问题,转化为二分图最大匹配求解。

Code

#include<cstdio>
#define maxn 1002
#define maxm 1000002
using namespace std;
struct edge{
	int to,next;
};
edge e[maxm];
int head[maxn],cnte;
void add(int u,int v){
	e[++cnte].to=v;
	e[cnte].next=head[u];
	head[u]=cnte;
}
int c[maxn],n1,n2,m;
bool vis[maxn];
bool dfs(int u){
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(vis[v])continue;
		vis[v]=1;
		if(!c[v]||dfs(c[v])){
			c[v]=u;
			return 1;
		}
	}
	return 0;
}
int match(){
	int ret=0;
	for(int i=1;i<=n1;i++){
		for(int j=1;j<=n2;j++)vis[j]=0;
		if(dfs(i))ret++;
	}
	return ret;
}
int main(){
	scanf("%d%d%d",&n1,&n2,&m);
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		if(u>n1||v>n2)continue;
		add(u,v);
	}
	printf("%d\n",match());
	return 0;
}

列队

问题描述

Sylvia是一个热爱学习的女孩子。
在平时的练习中,他总是能考到std以上的成绩,前段时间,他参加了一场练习赛,众所周知,机房是一个 \(n* n\) 的方阵。这天,他又打爆了std,感到十分无聊,便想要hack机房内同学的程序,他会挑选一整行或一整列的同学进行hack ( 而且每行每列只会hack一次 ),然而有些同学不是那么好惹,如果你hack了他两次,他会私下寻求解决,Sylvia十分害怕,只会hack他们一次。假设Sylvia的水平十分高超,每次hack都能成功,求他最多能hack多少次?

输入格式

第一行两个数 \(n,x\) 表示机房的大小和不好惹的同学个数
接下来 \(x\) 行,每行两个数 \(x,y\) 表示不好惹的同学坐标

输出格式

一个数表示最多hack多少次

样例输入

2 1
1 1

样例输出

6

样例说明

他可以hack第一行、第二行、第二列一共6次

数据规模

\(n\le 1000,x\le 4000\)

分析

对每个点 \((x,y)\) ,连边 \((x,y)\) ,表示点 \((x,y)\) 不能被hack两次,求出最大匹配,用 \(n* 2\) 减去最大匹配即可。

Code

#include<cstdio>
#define maxn 1002
#define maxm 1000002
using namespace std;
struct edge{
	int to,next;
};
edge e[maxm];
int head[maxn],cnte;
void add(int u,int v){
	e[++cnte].to=v;
	e[cnte].next=head[u];
	head[u]=cnte;
}
int n,m,c[maxn];
bool vis[maxn];
bool dfs(int u){
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(vis[v])continue;
		vis[v]=1;
		if(!c[v]||dfs(c[v])){
			c[v]=u;
			return 1;
		}
	}
	return 0;
}
int match(){
	int ret=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++)vis[j]=0;
		if(dfs(i))ret++;
	}
	return ret;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
	}
	printf("%d",n*((n<<1)-match()));
	return 0;
}

XJOI 1570 双子树

题意

这一天,随身带来的干粮豆吃玩了,必须有人去找些食物来吃才行。可Z4的懒惰是出了名的,谁会愿意去呢?经过一番商讨,大家最后决定以老规矩“猜十次”的方式决定人选。可怜的立方由于猜拳技术不过关,屡战屡败,只好踏上觅食的征途。
立方后来找到了两棵奇怪的树,由于它们的形态十分相像,双子座的立方一时兴起便把它们命名为“双子树”。可双子树的果实能不能吃呢?“管它呢,先摘了再说。”于是身手敏捷的立方便爬上其中一棵,摘下一个大大的果实。可就在此时,另一棵却有几个果实坠地。
立方细心一看,发现这双子树上的某些果实有一些细丝相连,只要摘下其中一棵树的某一个果实,另一棵树将会有相应的一个或多个(也可能没有)果实坠地而摔坏,这些果实都和摘下的果实用细丝相连。摔坏的果实当然不能吃了。而且,立方发现,那些细丝是没办法弄断的,除非摘下它两端的果实的其中一个。由于这时只有立方一人(好没义气的Z4啊……),他只能眼睁睁地看着它们摔坏。也就是说,立方无法同时摘取任一条细丝两端上的两个果实。于是,不同的摘法最后会得到不同数量的果实,而立方将会用随身带的一个容量为 \(V\) (表示能装 \(V\) 个果实)的大袋子将它们装好。
馋嘴的立方当然希望摘得越多越好啦,那么,他最多可以得到多少个果实呢?
特别地,任两个果实间最多只会有一条细丝相连,同一棵树上的果实间不会有细丝相连,当袋子装满后,立方的口还可以塞进一个。

输入格式

输入文件tree.in的第一行包括四个整数 \(V\)\(0<=V<=1000\) ),\(N_1,N_2\)\(M\) ,分别表示袋子的容量,第一棵树上的果实个数,第二棵树上的果实个数和细丝总数。为了方便计算,立方人为地把果实分别按 \(1..N_1\)\(1..N_2\) 标号。
接下来有M行,每行有两个整数 \(A,B\)\(1\le A\le N_1,1\le B\le N_2\)),表示第一棵树上的果实 \(A\) 和第二棵树上的果实 \(B\) 有一条细丝相连。

输出格式

输出文件tree.out仅有一个整数,表示立方最多能得到的果实个数。

样例输入&输出

样例1:

10 3 3 5
1 2
2 1
2 2
2 3
3 2

4

样例2:

2 4 4 4
1 1
2 2
3 3
4 4

3

数据范围

\(0\le V\le 1000,0\le N_1,N_2\le 500\)
时间限制:
1000
空间限制:
65536

提示:

样例1中,立方最后摘取了第一棵树上的1、3和第二棵树上的1、3。
样例2中,立方本来最多可以摘4个果实,但由于袋子容量仅为2,再加上他口中的一个,最后他只能得到3个。

分析

求出最大匹配数,答案即为 \(N_1+N_2-\) 最大匹配数。

Code

#include<cstdio>
#include<algorithm>
#define maxn 1002
#define maxm 1000002
using namespace std;
struct edge{
	int to,next;
};
edge e[maxm];
int head[maxn],cnte;
void add(int u,int v){
	e[++cnte].to=v;
	e[cnte].next=head[u];
	head[u]=cnte;
}
int c[maxn],V,n1,n2,m;
bool vis[maxn];
bool dfs(int u){
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(vis[v])continue;
		vis[v]=1;
		if(!c[v]||dfs(c[v])){
			c[v]=u;
			return 1;
		}
	}
	return 0;
}
int match(){
	int ret=0;
	for(int i=1;i<=n1;i++){
		for(int j=1;j<=n2;j++)vis[j]=0;
		if(dfs(i))ret++;
	}
	return ret;
}
int main(){
	scanf("%d%d%d%d",&V,&n1,&n2,&m);
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
	}
	printf("%d\n",min(V+1,n1+n2-match()));
	return 0;
}

带权最大的完美匹配

KM算法

Code

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=103,INF=1050000000;
int c[maxn],lc[maxn],ld[maxn],slack[maxn],n,g[maxn][maxn];
bool visc[maxn],visd[maxn];
bool dfs(int u){
    visd[u]=1;
    for(int v=1;v<=n;v++){
        int tmp=ld[u]+lc[v]-g[u][v];
        if(!visc[v]&&!tmp){
            visc[v]=1;
            if(!c[v]||dfs(c[v])){
                c[v]=u;
                return 1;
            }
        }
        else if(tmp<slack[v]){
            slack[v]=tmp;
        }
    }
    return 0;
}
int km(){
    for(int i=1;i<=n;i++){
        c[i]=lc[i]=ld[i]=0;
        for(int j=1;j<=n;j++){
            ld[i]=max(ld[i],g[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++)slack[j]=INF;
        while(1){
            for(int j=1;j<=n;j++)visc[j]=visd[j]=0;
            if(dfs(i))break;
            int res=INF;
            for(int v=1;v<=n;v++){
                if(!visc[v]){
                    res=min(res,slack[v]);
                }
            }
            for(int u=1;u<=n;u++){
                if(visd[u]){
                    ld[u]-=res;
                }
            }
            for(int v=1;v<=n;v++){
                if(visc[v]){
                    lc[v]+=res;
                }
                else{
                    slack[v]-=res;
                }
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)ans+=ld[i]+lc[i];
    return ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            scanf("%d",&g[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            g[i][j]=-g[i][j];
        }
    }
    printf("%d\n",-km());
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            g[i][j]=-g[i][j];
        }
    }
    printf("%d\n",km());
    return 0;
}
posted @ 2018-10-29 15:51  chc_1234567890  阅读(177)  评论(0编辑  收藏  举报