【二分图】

版权声明:本博客主要用于记录思考过程,方便复习,欢迎留言交流讨论~ https://blog.csdn.net/Floraqiu/article/details/81908886

模板1
模板2
讲解

判断是否为二分图(染色法)

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <list>
#define clr(x, y)  memset(x, y, sizeof(x))
#define MOD 1000000000+7
#define INF 0x3f3f3f3f

using namespace std;
const int maxn = 505;
int col[maxn];

bool bfs(int s)
{
    queue<int> q;
    q.push(s);
    color[s] = 1;
    while(!q.empty())
    {
        int from = q.front();
        q.pop();
        for(int i = 1; i <= n; i++)
        {
            if(mapp[from][i] && color[i] == -1)
            {
                q.push(i);
                color[i] = !color[from];//染成不同的颜色
            }
            if(mapp[from][i] && color[from] == color[i])//颜色有相同,则不是二分图
                return false;
        }
    }
    return true;
}

bool flag = true;
for(i = 1; i <= n; i++)
    if(color[i] == -1 && !bfs(i))  //遍历各个连通分支
    {
        flag = false;//flag为假说明不是二分图
        break;
    }

求二分图最大匹配的匈牙利算法

以 [过山车 HDU - 2063 ]为例
1、邻接矩阵

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <list>
#define clr(x, y)  memset(x, y, sizeof(x))
#define MOD 1000000000+7
#define INF 0x3f3f3f3f

using namespace std;
const int maxn = 505;
int k, m, n, mapp[maxn][maxn];
int link[maxn], vis[maxn];

bool found(int u)
{
    for(int i = 1; i <= n; i++)
    {
        if(!vis[i] && mapp[u][i])
        {
            vis[i] = 1;
            if(link[i] == -1 || found(link[i]))
            {
                link[i] = u;
                return true;
            }
        }
    }
    return false;
}

int sol()
{
    int num = 0;
    clr(link, -1);
    for(int i = 1; i <= m; i++)
    {
        clr(vis, 0);
        if(found(i)) num++;
    }
    return num;
}

int main()
{
    while(~scanf("%d%", &k) && k)
    {
        scanf("%d%d", &m, &n);
        clr(mapp, 0);
        for(int i = 1; i <= k; i++)
        {
            int x, y;
            scanf("%d%d", &x, &y);
            mapp[x][y] = 1;
        }
        printf("%d\n", sol());
    }
    return 0;
}

2、邻接表

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <list>
#define clr(x, y)  memset(x, y, sizeof(x))
#define MOD 1000000000+7
#define INF 0x3f3f3f3f

using namespace std;
const int maxn = 505;
int k, m, n;


struct maxmatch
{
    struct node
    {
        int to, next;
    } edge[maxn * maxn];

    int tot, head[maxn];
    int link[maxn], vis[maxn];

    void init()
    {
        tot = 0;
        clr(head, -1);
        clr(link, 0);
    }
    void addedge(int u, int v)
    {
        edge[tot].to = v;
        edge[tot].next = head[u];
        head[u] = tot++;
    }
    bool found(int u)
    {
        for(int i = head[u]; i != -1; i = edge[i].next)
        {
            int v = edge[i].to;
            if(!vis[v])
            {
                vis[v] = 1;
                if(!link[v] || found(link[v]))
                {
                    link[v] = u;
                    return true;
                }
            }
        }
        return false;
    }
    int match()
    {
        int num = 0;
        for(int i = 1; i <= m; i++)
        {
            clr(vis, 0);
            if(found(i)) num++;
        }
        return num;
    }
};


int main()
{
    maxmatch mp;
    while(~scanf("%d%", &k) && k)
    {
        scanf("%d%d", &m, &n);
        mp.init();
        for(int i = 1; i <= k; i++)
        {
            int x, y;
            scanf("%d%d", &x, &y);
            mp.addedge(x, y);
        }
        printf("%d\n", mp.match());
    }
    return 0;
}

二分图的最佳匹配 KM算法

朴素版 O(n^4)

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <list>
#define clr(x, y)  memset(x, y, sizeof(x))
#define MOD 1000000000+7
#define INF 0x3f3f3f3f

using namespace std;
const int maxn = 505;

bool findpath(int x)
{
    visx[x] = true;
    for(int y = 1; y <= ny; ++y)
    {
        if(!vis[y] && lx[x] + ly[y] == w[x][y])
        {
            vis[y] = true;
            if(match[y] == -1 || findpath(match[y]))
            {
                match[y] = x;
                return true;
            }
        }
    }
    return false;
}

void KM()
{
    for(int x = 1; x <= nx; ++x)
    {
        while(true)
        {
            clr(visx, false);//访问过X中的标记
            clr(visy, false);//访问过y中的标记
            if(findpath(x)) break;//找到了增广路,跳出继续寻找下一个
            else //更新可行顶标
            {
                int delta = INF;
                for(int i = 1; i <= nx; ++i)
                {
                    if(visx[i])//i在交错路中
                    {
                        for(int j = 1; j <= ny; ++j)
                        {
                            if(!vis[j])//j不在交错路中
                                delta = min(delta, lx[i] + ly[j] - w[i][j]);
                        }
                    }
                }
                for(int i = 1; i <= nx; ++i)
                    if(visx[i]) lx[i] -= delta;
                for(int j = 1; j <= ny; ++j)
                    if(visy[j]) ly[j] += delta;
            }
        }
    }
}

int solve()
{
    clr(match, -1);
    clr(ly, 0);
    for(int i = 1; i <= nx; ++i)
    {
        lx[i] = -INF;
        for(int j = 1; j <= ny; ++j)
            lx[i] = max(lx[i], w[i][j]);
    }
    KM();
    int ans = 0;
    for(int i = 0; i < ny; ++i)
        if(match[i] != -1)
            ans += G[match[i]][i];
    return ans;
}

优化版 O(n^3)

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <list>
#define clr(x, y)  memset(x, y, sizeof(x))
#define MOD 1000000000+7
#define INF 0x3f3f3f3f

using namespace std;
const int maxn = 505;

/*
实际上,O(n^4)的KM算法表现不俗,使用O(n^3)并不会很大的提高KM的运行效率
需要在O(1)的时间找到任意一条边,使用邻接矩阵存储更为方便
*/
int match[maxn], lx[maxn], ly[maxn], slack[maxn];
int G[maxn][maxn];
bool visx[maxn], visy[maxn];
int n, nx, ny, ans;

bool findpath(int x)
{
    int tempDelta;

    visx[x] = true;
    for(int y = 0; y < ny; ++y)//对每一条边都计算一遍delta
    {
        if(visy[y]) continue;
        tempDelta = lx[x] + ly[y] - G[x][y];
        if(tempDelta == 0)//说明(x,y)在相等子图里
        {
            visy[y] = true;
            if(match[y] == -1 || findpath(match[y]))
            {
                match[y] = x;
                return true;
            }
        }
        else slack[y] = min(slack[y], tempDelta);//(x,y)不在相等子图中且x在交错树中,y不在交错树中
    }
    return false;
}

void KM()
{
    for(int x = 0; x < nx; ++x)
    {
        clr(slack, INF);//每次换新的x结点都要初始化slack
        while(true)
        {
            //每次findpath()都要更新,所以必须清理vis
            clr(visx, false);
            clr(visy, false);
            if(findpath(x)) break;
            else
            {
                int delta = INF;
                for(int j = 0; j < ny; ++j)//之前dfs时,x一定在交错树中,dfs里对这些x都进行了松弛,这里直接找出最小的更新即可
                    if(!visy[j]) delta = min(delta, slack[j]);
                for(int i = 0; i < nx; ++i)
                    if(visx[i]) lx[i] -= delta;
                for(int j = 0; j < ny; ++j)
                    if(visy[j]) ly[j] += delta;
                    else slack[j] -= delta;
                    //修改顶标后,要把所有的slack值都减去delta
                    //这是因为所有在交错树中的lx[i] 减小了delta, 而不在交错树中的ly[j]没有变,w[i][j]也没变
                    //slack[j] = min(lx[i] + ly[j] -w[i][j])
            }
        }
    }
}

int solve()
{
    clr(match, -1);
    clr(ly, 0);
    for(int i = 0; i < nx; ++i)
    {
        lx[i] = -INF;
        for(int j = 0; j < ny; ++j)
            lx[i] = max(lx[i], G[i][j]);
    }
    KM();
    int ans = 0;
    for(int i = 0; i < ny; ++i)
        if(match[i] != -1)
        ans += G[match[i]][i];
    return ans;
}

猜你喜欢

转载自blog.csdn.net/Floraqiu/article/details/81908886