二分图 【总结】

来源:互联网 发布:泡妞宝鉴天地知我心二 编辑:程序博客网 时间:2024/06/05 11:37

路过发现有bug的地方,还请大神指出。 (ˇˍˇ) 想~


术语:最大匹配、最小路径覆盖、最小点覆盖、最大独立集、最大团、最大(小)权值匹配。

性质:

(1) 最小路径覆盖 = 节点数 - 最大匹配。

(2) 最小点覆盖 = 最大匹配。

(3) 最大独立集 = 节点数 - 最小点覆盖 = 节点数 - 最大匹配 = 补图的最大团。

(4) 最大团 = 补图的最大独立集。

(5) 对二分图染黑、白色,要求相邻节点颜色不同,问最多可以把多少个节点染成黑色 -> 求解最大独立集。

求解最大匹配算法有三个:匈牙利、HK、KM(大材小用了)

匈牙利算法:时间复杂度O(NM)

#include <cstdio>#include <cstring>#include <algorithm>#include <vector>#define MAXN (100+10)using namespace std;int N, M;vector<int> G[MAXN];bool used[MAXN];int match[MAXN];int DFS(int u){    for(int i = 0; i < G[u].size(); i++)    {        int v = G[u][i];        if(!used[v])        {            used[v] = true;            if(match[v] == -1 || DFS(match[v]))            {                match[v] = u;                return 1;            }        }    }    return 0;}int main(){    while(scanf("%d%d", &N, &M) != EOF)    {        for(int i = 1; i <= N; i++)            G[i].clear();        for(int i = 1; i <= M; i++)        {            int a, b;            scanf("%d%d", &a, &b);            G[a].push_back(b);        }        int ans = 0;        memset(match, -1, sizeof(match));        for(int i = 1; i <= N; i++)        {            memset(used, false, sizeof(used));            ans += DFS(i);        }        printf("%d\n", ans);    }    return 0;}





HK:时间复杂度O(sqrt(N)M)

#include <cstdio>#include <cstring>#include <queue>#include <algorithm>#define MAXN 5000+10using namespace std;int mx[MAXN], my[MAXN];//记录X和Y集元素的匹配点int dx[MAXN], dy[MAXN];//记录距离标号bool used[MAXN];//标记是否使用vector<int> G[MAXN];//存储二分图int n, m;//点数 边数int numx, numy;//记录X集元素个数 Y集元素个数int DFS(int u)//同匈牙利算法 多了层次图的判定{    for(int i = 0; i < G[u].size(); i++)    {        int v = G[u][i];        if(!used[v] && dy[v] == dx[u] + 1)        {            used[v] = true;            if(my[v] == -1 || DFS(my[v]))            {                my[v] = u;                mx[u] = v;                return 1;            }        }    }    return 0;}void HK_match(){    memset(mx, -1, sizeof(mx));    memset(my, -1, sizeof(my));    int ans = 0;    while(1)    {        bool flag = false;//标记是否找到可增广路径        memset(dx, 0, sizeof(dx));        memset(dy, 0, sizeof(dy));        queue<int> Q;        for(int i = 1; i <= numx; i++)            if(mx[i] == -1) Q.push(i);        while(!Q.empty())//寻找增广路        {            int u = Q.front(); Q.pop();            for(int i = 0; i < G[u].size(); i++)            {                int v = G[u][i];                if(!dy[v])                {                    dy[v] = dx[u] + 1;//感觉和 Dinic里面建立层次图很像                    if(my[v] == -1)//未匹配 说明找到增广路                    flag = true;                    else                    {                        dx[my[v]] = dx[u] + 1;                        Q.push(my[v]);                    }                }            }        }        if(!flag)//未找到增广路            break;        memset(used, false, sizeof(used));        for(int i = 1; i <= numx; i++)            if(mx[i] == -1)//没有匹配 就开始找匹配                ans += DFS(i);    }    printf("%d\n", ans);//若X、Y集没有详细划分,则numx = numy = n,最后结果除2。}int main(){    while(scanf("%d%d", &n, &m) != EOF)    {        getMap();//建图 用G存储        HK_match();//HK算法    }    return 0;}




KM 直接上求解最大(小)权匹配:求最小权值匹配,直接将边权取负,最后结果取负就可以了。

时间复杂度O(N*3)

#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 400#define INF 100000000//注意INF值要比所有边权值大using namespace std;int match[MAXN];//匹配int lx[MAXN], ly[MAXN];//顶标int slack[MAXN];//记录T集中节点的松弛量int Map[MAXN][MAXN];//存储原图S-T节点间的关系bool visx[MAXN], visy[MAXN];int nx, ny;//S集中节点数目 T集中节点数目int DFS(int x){    visx[x] = true;    for(int y = 0; y < ny; y++)    {        if(visy[y]) continue;        int t = lx[x] + ly[y] - Map[x][y];//判断边<x,y>是否在相等子图里面        if(t == 0)//边<x,y>在相等子图里面        {            visy[y] = true;            if(match[y] == -1 || DFS(match[y]))            {                match[y] = x;                return 1;            }        }        else if(slack[y] > t)// 更新松弛量            slack[y] = t;    }    return 0;}int KM(){    memset(match, -1, sizeof(match));    memset(ly, 0, sizeof(ly));    for(int x = 0; x < nx; x++)//lx[x]初始化为Map[x][y]中最大值    {        lx[x] = -INF;        for(int y = 0; y < ny; y++)        {            if(Map[x][y] > lx[x])                lx[x] = Map[x][y];        }    }    for(int x = 0; x < nx; x++)//匹配    {        for(int i = 0; i < ny; i++)//初始化T集中所有节点的 松弛量            slack[i] = INF;        while(1)        {            memset(visx, false, sizeof(visx));            memset(visy, false, sizeof(visy));            if(DFS(x)) break;//匹配成功            //匹配失败            int d = INF;            //寻找最小的松弛量d            for(int i = 0; i < ny; i++)            {                if(!visy[i] && d > slack[i])                    d = slack[i];            }            //修改顶标 为了有更大机会完美匹配            //S集里面所有点 全部减去最小松弛量d            for(int i = 0; i < nx; i++)            {                if(visx[i])                    lx[i] -= d;            }            //为了保证S-T的匹配不离开相等子图即对边<x,y>依旧有lx[x] + ly[y] == Map[x][y]            //T集里面所有点 全部增加最小松弛量d            for(int i = 0; i < ny; i++)            {                if(visy[i]                    ly[i] += d;                else//节点的松弛量 减少d                    slack[i] -= d;            }        }    }    int res = 0;    for(int i = 0; i < ny; i++)    {        if(match[i] != -1)//有匹配            res += Map[match[i]][i];//累加权值    }    return res;}int main(){    int N;    while(scanf("%d", &N) != EOF)    {        nx = ny = N;        for(int i = 0; i < N; i++)            for(int j = 0; j < N; j++)                scanf("%d", &Map[i][j]);//第i个和第j个匹配的权值        int ans = KM();        printf("%d\n", ans);    }    return 0;}




求解最大团算法:普通DFS 50个点就要跑8、9s,太慢。

优化DFS算法:

#include <cstdio>#include <cstring>#include <algorithm>#define MAXN (100+10)using namespace std;int N, M;int Clique[MAXN];//Clique[i]记录(i-N)这些节点可以构成的最大团int Map[MAXN][MAXN];//存储图int New[MAXN][MAXN];//每次建立从i-N这些节点  DFSint used[MAXN], Rec[MAXN];//Rec记录最大团里面的点int ans;//最大团节点数int DFS(int T, int cnt)//DFS遍历层次 计数{    if(T == 0)    {        if(ans < cnt)        {            ans = cnt;            memcpy(Rec, used, sizeof(used));            return 1;        }        return 0;    }    for(int i = 0; i < T; i++)    {        if(T - i + cnt <= ans) return 0;//剪枝一        int u = New[cnt][i];        if(Clique[u] + cnt <= ans) return 0;//剪枝二        int num = 0;        for(int j = i+1; j < T; j++)            if(Map[u][New[cnt][j]])                New[cnt+1][num++] = New[cnt][j];        used[cnt+1] = u;//记录当前节点        if(DFS(num, cnt+1)) return 1;    }    return 0;}void MaxClique(){    memset(Clique, 0, sizeof(Clique));    ans = 0;    for(int i = N; i >= 1; i--)    {        used[1] = i; int Size = 0;        for(int j = i+1; j <= N; j++)//根据后面的节点构建新图            if(Map[i][j])                New[1][Size++] = j;        DFS(Size, 1);        Clique[i] = ans;    }}int main(){    while(scanf("%d%d", &N, &M) != EOF)    {        memset(Map, 0, sizeof(Map));        for(int i = 1; i <= M; i++)        {            int a, b;            scanf("%d%d", &a, &b);            Map[a][b] = Map[b][a] = 1;        }        MaxClique();        printf("%d\n", ans);        for(int i = 1; i <= ans; i++)//输出最大团里面的节点        {            if(i > 1)                printf(" ");            printf("%d", Rec[i]);        }        printf("\n");    }    return 0;}





0 0