1. 程式人生 > 實用技巧 >【模板】最大匹配

【模板】最大匹配

二分圖匹配

匈牙利演算法

  • 時間複雜度\(O(nm)\)
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <queue>
using namespace std;
 
int mp[2100][2100]; // 圖的儲存矩陣
int n, m;
int ans;
bool vis[2100]; // 當前搜尋過程中是否被訪問過
int link[2100]; // y集合中的點在x集合中的匹配點 -1表示未匹配
 
bool find_(int x) {
    for (int i=1; i<=n; ++i) {
        if (mp[x][i] && !vis[i]) { // 有邊相連
            vis[i] = 1; // 標記該點
            if (link[i] == -1 || find_(link[i])) { //該點未匹配 或者匹配的點能找到增光路
                link[i] = x; // 刪掉偶數條邊 加進奇數條邊
                return true; // 找到增光路
            }
        }
    }
    return false;
}
 
void match() {
    //初始化
    ans = 0;
    memset(link, -1, sizeof(link));
 
    for (int i=1; i<=n; ++i) {
        memset(vis, 0, sizeof(vis)); // 從集合的每個點依次搜尋
        if (find_(i)) // 如果能搜尋到 匹配數加1
            ans++;
    }
    return;
}
 
int main() {
    while(cin >> n >> m) {
        memset(mp, 0, sizeof(mp));
 
        for (int i=0; i<m; ++i) {
            int x, y;
            cin >> x >> y;
            mp[x][y] = 1;
            mp[y][x] = 1;
        }
 
        //判斷是不是二分圖 過
 
        match();
        cout << ans/2 << endl;
    }
    return 0;
}

HK演算法

  • 時間複雜度為\(O(m\sqrt n)\)

  • 點的序號要從0開始!

  • 需要把nx,ny都賦值為n(點數)

const int MAXN = 1010;
const int MAXM = 1010*1010;

struct Edge {
    int v;
    int next;
} edge[MAXM];

struct node {
    double x, y;
    double v;
} a[MAXN], b[MAXN];

int nx, ny;
int cnt;
int t;
int dis;


int first[MAXN];
int xlink[MAXN], ylink[MAXN];
/*xlink[i]表示左集合頂點所匹配的右集合頂點序號,ylink[i]表示右集合i頂點匹配到的左集合頂點序號。*/
int dx[MAXN], dy[MAXN];
/*dx[i]表示左集合i頂點的距離編號,dy[i]表示右集合i頂點的距離編號*/
int vis[MAXN]; //尋找增廣路的標記陣列

void init() {
    cnt = 0;
    memset(first, -1, sizeof(first));
    memset(xlink, -1, sizeof(xlink));
    memset(ylink, -1, sizeof(ylink));
}

void read_graph(int u, int v) {
    edge[cnt].v = v;
    edge[cnt].next = first[u], first[u] = cnt++;
}

int bfs() {
    queue<int> q;
    dis = INF;
    memset(dx, -1, sizeof(dx));
    memset(dy, -1, sizeof(dy));
    for(int i = 0; i < nx; i++) {
        if(xlink[i] == -1) {
            q.push(i);
            dx[i] = 0;
        }
    }
    while(!q.empty()) {
        int u = q.front();
        q.pop();
        if(dx[u] > dis) break;
        for(int e = first[u]; e != -1; e = edge[e].next) {
            int v = edge[e].v;
            if(dy[v] == -1) {
                dy[v] = dx[u] + 1;
                if(ylink[v] == -1) dis = dy[v];
                else {
                    dx[ylink[v]] = dy[v]+1;
                    q.push(ylink[v]);
                }
            }
        }
    }
    return dis != INF;
}

int find(int u) {
    for(int e = first[u]; e != -1; e = edge[e].next) {
        int v = edge[e].v;
        if(!vis[v] && dy[v] == dx[u]+1) {
            vis[v] = 1;
            if(ylink[v] != -1 && dy[v] == dis) continue;
            if(ylink[v] == -1 || find(ylink[v])) {
                xlink[u] = v, ylink[v] = u;
                return 1;
            }
        }
    }
    return 0;
}

int MaxMatch() {
    int ans = 0;
    while(bfs()) {
        memset(vis, 0, sizeof(vis));
        for(int i = 0; i < nx; i++) if(xlink[i] == -1) {
                ans += find(i);
            }
    }
    return ans;
}

呼叫:

init();
for(int i = 0; i < m; i++) {
    if(l[edgee[i][0]] && edgee[i][1] != s && !l[edgee[i][1]])    read_graph(edgee[i][0],edgee[i][1]);
    if(l[edgee[i][1]] && edgee[i][0] != s && !l[edgee[i][0]])    read_graph(edgee[i][1],edgee[i][0]);
}
nx = n;
ny = n;
int ans = MaxMatch();

一般圖匹配(帶花樹)

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1050;
bool g[maxn][maxn],inque[maxn],inpath[maxn];
bool inhua[maxn];
int st,ed,newbase,ans,n;
int base[maxn],pre[maxn],match[maxn];
int head,tail,que[maxn]; 
int x[maxn],y[maxn],f[maxn],mp[maxn][maxn],ne,np;

void Push(int u)
{
    que[tail]=u;
    tail++;
    inque[u]=1;
}
int Pop()
{
    int res=que[head];
    head++;
    return res;
}

int lca(int u,int v)//尋找公共花祖先 
{
    memset(inpath,0,sizeof(inpath));
    while(1)
    {
        u=base[u];
        inpath[u]=1;
        if(u==st) break;
        u=pre[match[u]];    
    }    
    while(1)
    {
        v=base[v];
        if(inpath[v]) break;
        v=pre[match[v]];
    }
    return v;
} 
void reset(int u)//縮環 
{
    int v;
    while(base[u]!=newbase)
    {
        v=match[u];
        inhua[base[u]]=inhua[base[v]]=1;
        u=pre[v];
        if(base[u]!=newbase) pre[u]=v;
    }
} 
void contract(int u,int v)//
{
    newbase=lca(u,v);
    memset(inhua,0,sizeof(inhua));
    reset(u);
    reset(v);
    if(base[u]!=newbase) pre[u]=v;
    if(base[v]!=newbase) pre[v]=u;
    for(int i=1;i<=n;i++)
    {
        if(inhua[base[i]]){
            base[i]=newbase;
            if(!inque[i])
                Push(i);
        }
    }
}
void findaug()
{
    memset(inque,0,sizeof(inque));
    memset(pre,0,sizeof(pre));
    for(int i=1;i<=n;i++)//並查集 
        base[i]=i;
    head=tail=1;
    Push(st);
    ed=0;
    while(head<tail)
    {
        int u=Pop();
        for(int v=1;v<=n;v++)
        {
            if(g[u][v]&&(base[u]!=base[v])&&match[u]!=v)
            {
                if(v==st||(match[v]>0)&&pre[match[v]]>0)//成環 
                    contract(u,v);
                else if(pre[v]==0)
                {
                    pre[v]=u;
                    if(match[v]>0)
                        Push(match[v]);
                    else//找到增廣路 
                    {
                        ed=v;
                        return ;    
                    }    
                }
            }
        }
    }
}
void aug()
{
    int u,v,w;
    u=ed;
    while(u>0)
    {
        v=pre[u];
        w=match[v];
        match[v]=u;
        match[u]=v;
        u=w;
    }
}
void edmonds()//匹配 
{
    memset(match,0,sizeof(match));
    for(int u=1;u<=n;u++)
    {
        if(match[u]==0)
        {
            st=u;
            findaug();//以st開始尋找增廣路 
            if(ed>0) aug();//找到增廣路  重新染色,反向 
        }
    }
}
//以上是帶花樹求最大匹配演算法  不用看 
 
void create()//建圖 
{
    n=0;
    memset(g,0,sizeof(g));
    for(int i=1;i<=np;i++)
        for(int j=1;j<=f[i];j++)
            mp[i][j]=++n;//拆點,給每個度的點編號 
    for(int i=0;i<ne;i++)
    {//此時n+1代表x,n+2代表y 
        for(int j=1;j<=f[x[i]];j++)
            g[mp[x[i]][j]][n+1]=g[n+1][mp[x[i]][j]]=1;//每個度的點與對應的x,y相連 
        for(int j=1;j<=f[y[i]];j++)
            g[mp[y[i]][j]][n+2]=g[n+2][mp[y[i]][j]]=1;
        g[n+1][n+2]=g[n+2][n+1]=1;//x與y相連 
        n+=2;
    }    
}
void print()
{
    ans=0;
    for(int i=1;i<=n;i++)
        if(match[i]!=0)
        {
            ans++;
//            if(match[i]>i)
//            cout<<"_____"<<i<<' '<<match[i]<<endl;
        }
    //cout<<"******"<<ans<<' '<<n<<endl;
    if(ans==n)    printf("YES\n");
    else    printf("NO\n");
}
int main()
{
    int t,k=0;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&np,&ne);
        for(int i=0;i<ne;i++)
            scanf("%d%d",&x[i],&y[i]);
        for(int i=1;i<=np;i++)
            scanf("%d",&f[i]);
        printf("Case %d: ",++k);
        create();
        edmonds();
        print();    
    }     
    return 0;
}