【模板】匹配问题

二分图匹配

匈牙利算法

  • 时间复杂度(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(msqrt 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();

KM算法 BFS优化

最优匹配——定理:设(M)是一个带权完全二分图(G)的一个完备匹配,给每个顶点一个可行顶标(第(i)(x)顶点的可行顶标用 (x_i)表示,第(j)个y顶点的可行顶标用(y_j)表示),如果对所有的边((i,j)) in (G),都有(x_i+y_j≥w_{i,j})成立((w_{i,j})表示边的权),且对所有的边((i,j)) in (M),都有 成立,则(M)是图(G)(x_i+y_j≥w_{i,j})一个最优匹配。

  • 时间复杂度(O(n^3))

  • 最小权值匹配,边权取反,答案取反

  • (x_i+y_j>=w_{i,j}),最小顶标和等价于最大权值匹配

    (x_i+y_j<=w_{i,j}),最大顶标和等价于最小权值匹配

typedef long long ll;
const int N = 405;
const ll INF = LONG_LONG_MAX;
struct KM
{
    int link_x[N], link_y[N], n, nx, ny;
    bool visx[N], visy[N];
    int que[N << 1], top, fail, pre[N];
    ll mp[N][N], hx[N], hy[N], slk[N];
    inline int check(int i)
    {
        visx[i] =true;
        if(link_x[i])
        {
            que[fail++] = link_x[i];
            return visy[link_x[i]] = true;
        }
        while(i)
        {
            link_x[i] = pre[i];
            swap(i, link_y[pre[i]]);
        }
        return 0;
    }
    void bfs(int S)
    {
        for(int i=1; i<=n; i++)
        {
            slk[i] = INF;
            visx[i] = visy[i] = false;
        }
        top = 0; fail = 1;
        que[0] = S;
        visy[S] = true;
        while(true)
        {
            ll d;
            while(top < fail)
            {
                for(int i = 1, j = que[top++]; i <= n; i++)
                {
                    if(!visx[i] && slk[i] >= (d = hx[i] + hy[j] - mp[i][j]))
                    {
                        pre[i] = j;
                        if(d) slk[i] = d;
                        else if(!check(i)) return;
                    }
                }
            }
            d = INF;
            for(int i=1; i<=n; i++)
            {
                if(!visx[i] && d > slk[i]) d = slk[i];
            }
            for(int i=1; i<=n; i++)
            {
                if(visx[i]) hx[i] += d;
                else slk[i] -= d;
                if(visy[i]) hy[i] -= d;
            }
            for(int i=1; i<=n; i++)
            {
                if(!visx[i] && !slk[i] && !check(i)) return;
            }
        }
    }
    void init(int cntx, int cnty)
    {
        nx = cntx; ny = cnty; n = max(nx, ny);
        top = fail = 0;
        for (int i = 1; i <= n; i++) {
            link_x[i] = link_y[i] = pre[i] = 0;
            hx[i] = hy[i] = 0;
            for (int j = 1; j <= n; j++) mp[i][j] = 0;
        }
    }
    void solve() {
        for(int i=1; i<=n; i++) for(int j=1; j<=n; j++)
            if(hx[i] < mp[i][j]) hx[i] = mp[i][j];
        ll ans = 0;
        for (int i = 1; i <= n; i++) bfs(i);
        for (int i = 1; i <= nx; i++) ans += mp[i][link_x[i]];
        printf("%lld
", ans); // 输出最大权值
        
        /*
        for (int i = 1; i <= nx; i++) if(!mp[i][link_x[i]])
            link_x[i] = 0; // 如果不存在该边则置零
        for (int i = 1; i <= nx; i++) {
            if(i != 1) printf(" ");
            printf("%d", link_x[i]); // 输出每一组匹配
        }
        printf("
");
         */
    }
}km;

调用

int n; scanf("%d", &n);
km.init(n, n);
for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j++) {
        ll w; scanf("%lld", &w);
        km.mp[i][j] = w;
    }
}
km.solve();

一般图匹配(带花树)

#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
");
    else    printf("NO
");
}
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;
}
原文地址:https://www.cnblogs.com/Waldeinsamkeit/p/13450798.html