关于二分匹配的几道简单题

最近重新学习了二分匹配的算法,并趁热打铁做了几道简单的题。

这个博客讲的二分匹配的算法比较通俗易懂,https://blog.csdn.net/dark_scope/article/details/8880547

HDU 1150

把每一个任务在两种机器下的模式看作一条边,建图之后求最小顶点覆盖集(用最少的点,使每一条边都与其中一个点相关联),而最小顶点覆盖集=最大匹配,此题在输入的时候把模式为0的去掉,因为两种机器初始状态为0。

#include <stdio.h>
#include <string.h>

const int maxn = 110;
int G[maxn][maxn],vis[maxn],match[maxn];
int nx,ny,m;

bool find(int u)
{
    for(int i = 0; i < ny; i++){
        if(G[u][i] && !vis[i]){
            vis[i] = 1;
            if(match[i] == -1 || find(match[i])){
                match[i] = u;
                return true;
            }
        }
    }
    return false;
}
int max_match()
{
    int ans = 0;
    memset(match,-1,sizeof(match));
    for(int i = 0; i < nx; i++){
        memset(vis,0,sizeof(vis));
        if(find(i)){
            ans++;
        }
    }
    return ans;
}
int main(void)
{
    while(scanf("%d",&nx) != EOF && nx){
        scanf("%d%d",&ny,&m);
        memset(G,0,sizeof(G));
        for(int i = 1; i <= m; i++){
            int id,x,y;
            scanf("%d%d%d",&id,&x,&y);
            if(x && y){
                G[x][y] = 1;
            }
        }
        printf("%d\n",max_match());
    }
    return 0;
}

HDU 1151

给出一个有向图,士兵沿着路的方向走,问至少需要多少士兵,能把所有边都走一遍。其实就是求有向无环图的最小路径覆盖,有向无环图的最小路径覆盖=顶点数-最大匹配数

#include <stdio.h>
#include <string.h>

const int maxn = 150;
int match[maxn],G[maxn][maxn],vis[maxn];
int n,m;

bool find(int u)
{
    for(int i = 1; i <= n; i++){
        if(G[u][i] && !vis[i]){
            vis[i] = 1;
            if(match[i] == -1 || find(match[i])){
                match[i] = u;
                return true;
            }
        }
    }
    return false;
}
int max_match()
{
    int ans = 0;
    memset(match,-1,sizeof(match));
    for(int i = 1; i <= n; i++){
        memset(vis,0,sizeof(vis));
        if(find(i)){
            ans++;
        }
    }
    return ans;
}
int main(void)
{
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        memset(G,0,sizeof(G));
        for(int i = 1; i <= m; i++){
            int a,b;
            scanf("%d%d",&a,&b);
            G[a][b] = 1;
        }
        printf("%d\n",n - max_match());
    }
    return 0;
}

HDU 1068

找出一个最大的集合,使这个集合的任意两个同学都没有浪漫的关系。求的是最大独立集(在n个点的图中,选出一个最大点集m,并且这m个点任意两点之间都没有边相连),最大独立集=顶点数-最大匹配数,这道题并没有指定性别,所以要减去最大匹配/2。

#include <stdio.h>
#include <string.h>

const int maxn = 1005;
int match[maxn],G[maxn][maxn],vis[maxn];
int n,m;

bool find(int u)
{
    for(int i = 0; i < n; i++){
        if(G[u][i] && !vis[i]){
            vis[i] = 1;
            if(match[i] == -1 || find(match[i])){
                match[i] = u;
                return true;
            }
        }
    }
    return false;
}
int max_match()
{
    int ans = 0;
    memset(match,-1,sizeof(match));
    for(int i = 0; i < n; i++){
        memset(vis,0,sizeof(vis));
        if(find(i)){
            ans++;
        }
    }
    return ans;
}
int main(void)
{
    while(scanf("%d",&n) != EOF){
        memset(G,0,sizeof(G));
        int id,num;
        for(int i = 0; i < n; i++){
            scanf("%d: (%d)",&id,&num);
            while(num--){
                scanf("%d",&id);
                G[i][id] = 1;
            }
        }
        printf("%d\n",n -  max_match() / 2);
    }
    return 0;
}

HDU 1281

对于一个点(i,j),如果放上车,就在i,j之间连上一条边,放的车的最大个数就是最大匹配数,先求出最大匹配数,然后枚举每一个点,求出去掉这个点之后的最大匹配数,如果比之前的少,说明是重要点。

#include <stdio.h>
#include <string.h>

int match[105];
int x[10005],y[10005];
int n,m,k;
int vis[105];
int a[105][105];
int b[105][105];

int find(int u){
	
	for(int i=1;i<=m;i++){
		
		if(vis[i] == 0 && a[u][i] && !b[u][i]){
			vis[i] = 1;
			if(match[i] == 0 || find(match[i])){
				match[i] = u;
				return 1;
			}
		}
	}
	return 0;
}
int main(void){
	int count = 0;
	while(scanf("%d%d%d",&n,&m,&k)!=EOF){
		
		
		memset(match,0,sizeof(match));
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		
		
		for(int i=1;i<=k;i++){
			scanf("%d%d",&x[i],&y[i]);
			a[x[i]][y[i]] = 1;
		}
		
		int ans=0;
		for(int i=1;i<=n;i++){
			memset(vis,0,sizeof(vis));
			if(find(i)){
				ans++;
			}
		}
		int anss = 0;
		for(int i=1;i<=k;i++){
			
			b[x[i]][y[i]] = 1;
			memset(match,0,sizeof(match));
			int temp=0;
			for(int j=1;j<=n;j++){
				memset(vis,0,sizeof(vis));
				if(find(j)){
					temp++;
				}
			}
			if(temp < ans){
				anss++;
			}
			b[x[i]][y[i]] = 0;
		}
		printf("Board %d have %d important blanks for %d chessmen.\n",++count,anss,ans);
	}
	return 0;
}

HDU 1498

给出一个矩阵,每一个数代表一种气球,求有多少种气球,每次刷掉一行或一列,在最多刷k次的情况下刷不完,按字典序输出,不存在则输出-1。还是根据行列坐标建图,并且图里存储的是哪种气球,选出最少的行号或列号覆盖所有储存这种颜色的所有边,最小点覆盖集,即求最大匹配,然后与k比较。

#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;

const int maxn = 105;
int ans[maxn];
int match[maxn],G[maxn][maxn],vis[maxn],used[maxn];
int n,c,k;

bool find(int u)
{
    for(int i = 1; i <= n; i++){
        if(G[u][i] == c && !vis[i]){
            vis[i] = 1;
            if(match[i] == -1 || find(match[i])){
                match[i] = u;
                return true;
            }
        }
    }
    return false;
}
int max_match()
{
    int ans = 0;
    memset(match,-1,sizeof(match));
    for(int i = 1; i <= n; i++){
        memset(vis,0,sizeof(vis));
        if(find(i)){
            ans++;
        }
    }
    return ans;
}
int main(void)
{
    while(scanf("%d%d",&n,&k) != EOF){
        if(n == 0 && k == 0){
            break;
        }
        memset(G,0,sizeof(G));
        memset(used,0,sizeof(used));
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                scanf("%d",&G[i][j]);
            }
        }
        int cnt = 0;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                if(!used[G[i][j]]){
                    used[G[i][j]] = 1;
                    c = G[i][j];
                    int num = max_match();
                    if(num > k){
                        ans[cnt++] = c;
                    }
                }
            }
        }
        if(cnt == 0){
            printf("-1\n");
        }
        else{
            sort(ans,ans + cnt);
            for(int i = 0; i < cnt; i++){
                if(i == cnt - 1){
                    printf("%d\n",ans[i]);
                }
                else{
                    printf("%d ",ans[i]);
                }
            }
        }

    }
    return 0;
}

HDU 1528

很明显的求最大匹配,就是需要判断一下两张牌的大小关系。

#include <stdio.h>
#include <string.h>
#include <map>

using namespace std;

char a[50][2],b[50][2];
int match[50],vis[50];
int n;
map <char,int> mp;
void init()
{
    for(char i = '2'; i <= '9'; i++){
        mp[i] = i - '0';
    }
    mp['T'] = 10;
    mp['J'] = 11;
    mp['Q'] = 12;
    mp['K'] = 13;
    mp['A'] = 14;
    mp['H'] = 3;
    mp['S'] = 2;
    mp['D'] = 1;
    mp['C'] = 0;
}
bool cmp(int i,int j)
{
    int ansb = mp[b[i][0]];
    int ansa = mp[a[j][0]];
    if(ansa == ansb){
        ansb += mp[b[i][1]];
        ansa += mp[a[j][1]];
    }
    else{
        return ansb > ansa;
    }
    return ansb > ansa;
}
bool find(int u)
{
    for(int i = 1; i <= n; i++){
        if(cmp(u,i) && !vis[i]){
            vis[i] = 1;
            if(match[i] == -1 || find(match[i])){
                match[i] = u;
                return true;
            }
        }
    }
    return false;
}
int max_match()
{
    int ans = 0;
    memset(match,-1,sizeof(match));
    for(int i = 1; i <= n; i++){
        memset(vis,0,sizeof(vis));
        if(find(i)){
            ans++;
        }
    }
    return ans++;
}
int main(void)
{
    int T;
    init();
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i = 1; i <= n; i++){
            scanf("%s",a[i]);
        }
        for(int i = 1; i <= n; i++){
            scanf("%s",b[i]);
        }
        printf("%d\n",max_match());
    }
    return 0;
}



HDU 1507

这道题建出二分图是关键,先给每个空白点编号,然后横纵坐标相加为奇数的空白点为X集合,横纵坐标相加为偶数的的空白点为Y集合,如果两个点,一个属于X,一个属于Y,并且这两个点上或下或左或右相邻,两个点连接一条边,这样就构建除了二分图,求最大匹配即可,如何打印出结果看代码。

#include <stdio.h>
#include <string.h>

const int maxn = 105;
int G[maxn][maxn],vis[maxn];
int bin[maxn][maxn],match[maxn];
int num[maxn][maxn];
int node[maxn][2];
int n,m;
int to[4][2] = {0,1,1,0,-1,0,0,-1};
int cnt;

bool find(int u)
{
    for(int i = 1; i < cnt; i++){
        if(bin[u][i] && !vis[i]){
            vis[i] = 1;
            if(match[i] == -1 || find(match[i])){
                match[i] = u;
                return true;
            }
        }
    }
    return false;
}
int max_match()
{
    int ans = 0;
    memset(match,-1,sizeof(match));
    for(int i = 1; i < cnt; i++){
        memset(vis,0,sizeof(vis));
        if(find(i)){
            ans++;
        }
    }
    return ans;
}
int main(void)
{
    while(scanf("%d%d",&n,&m) != EOF){
        if(n + m == 0){
            break;
        }
        int k;
        scanf("%d",&k);
        memset(G,0,sizeof(G));
        memset(bin,0,sizeof(bin));
        memset(node,0,sizeof(node));
        memset(num,0,sizeof(num));
        for(int i = 1; i <= k; i++){
            int a,b;
            scanf("%d%d",&a,&b);
            G[a][b] = 1;
        }
        cnt = 1;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                if(!G[i][j]){
                    node[cnt][0] = i;
                    node[cnt][1] = j;
                    num[i][j] = cnt++;
                }
            }
        }
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                if((i + j) & 1 && num[i][j]){
                    for(int q = 0; q <= 3; q++){
                        int x = i + to[q][0];
                        int y = j + to[q][1];

                        if(x >= 1 && y >= 1 && x <= n && y <= m && num[x][y]){
                            //printf("%d___%d\n",num[i][j],num[x][y]);
                            bin[num[i][j]][num[x][y]] = 1;
                        }
                    }
                }
            }
        }
        printf("%d\n",max_match());
        for(int i = 1; i < cnt; i++){
            if(match[i] != -1){
                printf("(%d,%d)--(%d,%d)\n",node[i][0],node[i][1],node[match[i]][0],node[match[i]][1]);
            }
        }
        printf("\n");
    }
    return 0;
}

POJ 2724

因为*同时消毒两次,所以尽量用*,将所有操作解码,用一个vis数组标记,对于二进制只有一位不同的两个点连接一条边,求最大独立集,注意这个图是有向图,因为两个只差一位的数合并成一个,建图时是遍历所有的点,一个点用完后和它连接的点会再次出现,而我们要求只匹配一次即可。

#include <stdio.h>
#include <string.h>
#include <vector>

using namespace std;
const int maxn = 1 << 11;
int vis[maxn],used[maxn];
int match[maxn],n,m,cnt;
vector<int> G[maxn];


void input(){
	for(int i = 0; i < (1 << n); i++){
		G[i].clear();
		vis[i] = 0;
	}
	
	int flag;
	char op[15];
	int val;
	for(int i = 0; i < m; i++){
		flag = -1;
		val = 0;
		scanf("%s",op);
		for(int j = 0; j < n; j++){
			if(op[j] == '*'){
				flag = j;
			}
			else if(op[j] == '1'){
				val |= (1 << j);
			}
		}
		vis[val] = 1;
		if(flag != -1){
			val |= (1 << flag);
			vis[val] = 1;
		}
	}
	cnt = 0;
	for(int i = 0; i < (1 << n); i++){
		if(vis[i]){
			cnt++;
			for(int j = 0; j < (1 << n); j++){
				if(vis[j]){
					int temp = i ^ j;
					if(temp && !((temp) & (temp - 1))){
						G[i].push_back(j);
						G[j].push_back(i);
					}
				}
			}
		}
	}
}
bool find(int i){
	for(int j = 0; j < G[i].size(); j++){
		int v = G[i][j];
		if(!used[v]){
			used[v] = 1;
			if(match[v] == -1 || find(match[v])){
				match[v] = i;
				return true;
			}
		}
	}
	return false;
}
int max_match(){
	memset(match,-1,sizeof(match));
	int ans = 0;
	for(int i = 0; i < (1 << n); i++){
		if(vis[i]){
			memset(used,0,sizeof(used));
		    if(find(i)){
		    	ans++;
			}
		}
		
	}
	return ans;
}
int main(void){
	
	while(scanf("%d%d",&n,&m) != EOF && (n + m)){
		input();
		printf("%d\n",cnt - max_match() / 2);
	}
	return 0;
} 

POJ 3216

先对任意两条街道求最短路,用floyd就行,然后建图,两个任务i,j,如果完成i之后在到达j时的时间小于最晚到达j的时间,则i与j之间加一条边,求最小路径覆盖即可。

#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;


const int INF = 0x3f3f3f3f;
int dis[25][25];
int G[205][205],match[205],vis[205];
int p[205],t[205],d[205];

int Q,M;
void floyd()
{
    for(int k = 1; k <= Q; k++){
        for(int i = 1; i <= Q; i++){
            for(int j = 1; j <= Q; j++){
                if(dis[i][j] > dis[i][k] + dis[k][j]){
                    dis[i][j] = dis[i][k] + dis[k][j];
                }
            }
        }
    }
}

void input()
{
    for(int i = 1; i <= Q; i++){
        for(int j = 1; j <= Q; j++){
            scanf("%d",&dis[i][j]);
            if(dis[i][j] == -1){
                dis[i][j] = INF;
            }
        }
    }
    for(int i = 1; i <= M; i++){
        scanf("%d%d%d",&p[i],&t[i],&d[i]);
    }
}
void get_map()
{
    for(int i = 1; i <= M; i++){
        for(int j = 1; j <= M; j++){
            if(t[i] + dis[p[i]][p[j]] + d[i] <= t[j]){
                G[i][j] = 1;
            }
        }
    }
}
void init()
{
    for(int i = 1; i <= M; i++){
        for(int j = 1; j <= M; j++){
            G[i][j] = 0;
        }
    }
}
bool find(int u)
{
    for(int i = 1; i <= M; i++){
        if(G[u][i] && !vis[i]){
            vis[i] = 1;
            if(match[i] == -1 || find(match[i])){
                match[i] = u;
                return true;
            }
        }
    }
    return false;
}
int max_match()
{
    int ans = 0;
    memset(match,-1,sizeof(match));
    for(int i = 1; i <= M; i++){
        memset(vis,0,sizeof(vis));
        if(find(i)){
            ans++;
        }
    }
    return ans;
}

int main(void)
{
    while(scanf("%d%d",&Q,&M) && (Q + M)){
        init();
        input();
        floyd();
        get_map();
        printf("%d\n",M - max_match());
    }
    return 0;
}




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值