1. 程式人生 > >關於二分匹配的幾道簡單題

關於二分匹配的幾道簡單題

最近重新學習了二分匹配的演算法,並趁熱打鐵做了幾道簡單的題。

把每一個任務在兩種機器下的模式看作一條邊,建圖之後求最小頂點覆蓋集(用最少的點,使每一條邊都與其中一個點相關聯),而最小頂點覆蓋集=最大匹配,此題在輸入的時候把模式為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;
}

給出一個有向圖,士兵沿著路的方向走,問至少需要多少士兵,能把所有邊都走一遍。其實就是求有向無環圖的最小路徑覆蓋,有向無環圖的最小路徑覆蓋=頂點數-最大匹配數

#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;
}

找出一個最大的集合,使這個集合的任意兩個同學都沒有浪漫的關係。求的是最大獨立集(在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;
}

對於一個點(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;
}

給出一個矩陣,每一個數代表一種氣球,求有多少種氣球,每次刷掉一行或一列,在最多刷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;
}

很明顯的求最大匹配,就是需要判斷一下兩張牌的大小關係。

#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;
}



這道題建出二分圖是關鍵,先給每個空白點編號,然後橫縱座標相加為奇數的空白點為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;
}

因為*同時消毒兩次,所以儘量用*,將所有操作解碼,用一個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;
} 

先對任意兩條街道求最短路,用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;
}