zoukankan      html  css  js  c++  java
  • 二分图匹配

    概念

    二分图

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

    匹配

    若二分图 (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
    ",match());
    	return 0;
    }
    

    例题

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

    二分图匹配模板题。

    POJ - 3041 Asteroids

    题意

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

    分析

    我们设 (u_i(iin [1,n])) (行号集合)为二分图左侧顶点, (v_i(iin [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
    ",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次

    数据规模

    (nle 1000,xle 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)(1le Ale N_1,1le Ble 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
    

    数据范围

    (0le Vle 1000,0le N_1,N_2le 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
    ",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
    ",-km());
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                g[i][j]=-g[i][j];
            }
        }
        printf("%d
    ",km());
        return 0;
    }
    
  • 相关阅读:
    java痛苦学习之路[十]--日常问题汇总
    我有回来了
    LeetCode[Array]----3Sum
    在Visual Stdio 2012中编译执行JM18.6的方法
    Android沉浸式状态栏兼容4.4手机的实现
    HTML5学习笔记(九):选择器详解
    HTML5学习笔记(八):CSS定位
    HTML5学习笔记(七):CSS盒子模型
    HTML5学习笔记(六):CSS基本样式
    HTML5学习笔记(五):CSS基础
  • 原文地址:https://www.cnblogs.com/BlogOfchc1234567890/p/9871073.html
Copyright © 2011-2022 走看看