二分图Bipartite Graph

· · 个人记录

定义:

二分图又称作二部图,是图论中的一种特殊模型。

设G=(V,E)是一个无向图。如果顶点集 V可分割为两个互不相交的子集X和Y,并且图中每条边连接的两个顶点一个在 X中,另一个在Y中,则称图G为二分图。

性质

当且仅当无向图G的每一个环的结点数均是偶数时,图G才是一个二分图。

如果无环,相当于每的结点数为0,故也视为二分图

判定

如果一个图是连通的,可染色法判定是否二分图:

【假设】把X部的结点颜色设为0Y部的颜色设为1

也就是说,同一部的顶点之间不应该有边

存储结构:无向图

struct Edge
{
    int to;
    Edge* next = NULL;
    int w;//权值
};
class UndirectedGraph
{
public:
    int n;
    Edge* head[N];
    void init(int n,int e)
    {
        //n代表左部点的个数,m代表右部点的个数,e代表边的条数
        this->n = n;
        for (int i = 0; i < n; i++)
        {
            head[i] = (Edge*)malloc(sizeof(Edge));
            head[i]->next = NULL;
            head[i]->to = 0;
        }
        while (e--)
        {
            int s, t;
            cin >> s >> t;
            Edge* now1;
            Edge* now2;
            now1 = (Edge*)malloc(sizeof(Edge));
            now2 = (Edge*)malloc(sizeof(Edge));
            now1->to = t;
            now2->to = s;
            now1->next = this->head[s]->next;
            now2->next = this->head[t]->next;
            this->head[s]->next = now1;
            this->head[t]->next = now2;
        }
        return;
    }
};

判定函数

bool DFS(UndirectedGraph G, vector<int>& color, int v)
{
    //对联通子图进行染色检查
    for (Edge* now = G.head[v]->next; now != NULL; now = now->next)
    {
        if (color[now->to] == 0)
        {              
            color[now->to] = color[v] % 2 + 1;//染上不同的颜色
            if (!DFS(G, color, now->to))
                return false;
        }
        else
        {
            if (now->to != v && color[now->to] == color[v])//如果搜到一条颜色相同的边
            {
                return false;
            }
        }

    }
    return true;
}
bool Is_BiPartGraph(UndirectedGraph G)
{
    //染色法判断二分图
    //用DPS搜
    vector<int> color(G.n);
    for (int i = 0; i < G.n; i++)
        color[i] = 0;
    for (int i = 0; i < G.n; i++)
    {
        if (color[i] == 0)
        {
            color[i] = 1;
            if (!DFS(G, color, i))
                return false;
        }
    }
    return true;
}

主函数

int main()
{
    UndirectedGraph G;
    int n, e;
    cin >> n >> e;
    G.init(n, e);
    std::cout << Is_BiPartGraph(G);
    return 0;
}

测试数据

6 6
0 3
0 5
1 3
1 4
1 5
2 5 

输出

1

匹配

匈牙利算法

【预备】增广路径

【预备】反色

算法

代码

struct Edge
{
    int to;
    Edge* next = NULL;
    int w;//权值
};
class BiPartGraph
{
private:
    Edge* head[N];
    int n;
    int m;
    int e;
    int visit[N];
    int match[N];
    bool maxmatch(int v);
public:
    void init(int n,int m,int e);
    int MaxMatch();
};
int BiPartGraph::MaxMatch()
{
    int ans = 0;
    memset(this->visit, 0, sizeof(this->visit));
    memset(this->match, 0, sizeof(this->match));
    for (int i = 1; i <= this->n; i++)
    {
        **memset(this->visit, 0, sizeof(this->visit));**
        if (maxmatch(i))
            ans++;
    }
    return ans;
}
bool BiPartGraph::maxmatch(int v)
{    
    for (Edge* now = this->head[v]->next; now != NULL; now = now->next)
    {
        if (!this->visit[now->to])
        {
            visit[now->to] = 1;
            if (!this->match[now->to] || this->maxmatch(this->match[now->to]))
            {
                match[now->to] = v;
                return true;
            }
        }
    }
    return false;
}
void BiPartGraph::init(int n,int m,int e)
{
    //n代表左部点的个数,m代表右部点的个数,e代表边的条数
    this->n = n;
    this->m = m;
    this->e = e;
    for (int i = 0; i < n + m + 5; i++)
    {
        head[i] = (Edge*)malloc(sizeof(Edge));
        this->head[i]->to = 0;
        this->head[i]->next = NULL;        
    }
    while (e--)
    {
        //把n个点存在前面,m个点存在后面
        int s, t;
        cin >> s >> t;
        t = this->n + t;
        Edge* now1;
        Edge* now2;
        now1 = (Edge*)malloc(sizeof(Edge));
        now2 = (Edge*)malloc(sizeof(Edge));
        now1->to = t;
        now2->to = s;
        now1->next = now2->next = NULL;
        Edge* now = this->head[s];
        while (now->next != NULL)
        {
            if (now->to == t)
                break;
            else
                now=now->next;
        }
        if (now->to != t && now->next == NULL)
        {
            now->next = now1;
        }
        now = this->head[t];
        while (now->next != NULL)
        {
            if (now->to == s)
                break;
            else
                now=now->next;
        }
        if (now->to != s && now->next == NULL)
        {
            now->next = now2;
        }
    }
    return;
}
int main()
{
    BiPartGraph G;
    int n, m, e;
    cin >> n >> m >> e;
    G.init(n, m, e);
    cout << G.MaxMatch();
    return 0;
}

解题记录

二分图最优匹配:

【预备】最大权匹配:

使所选边权和最大的匹配

【预备】完全匹配(完备匹配):

若X部和Y部中所有的顶点都是匹配M中的端点,则称M为完全匹配

完备匹配下的最大权匹配

【预备】可行顶标

可行顶标数组是人为添加的两个数组,一般为lx[n],ly[y]

初始化

一般来说,在初始状态下,选择顶点较少的部分作为`x部`

关于这个数组具体的使用方法,我们在后面讨论

性质

$lx[x]+ly[y]>=weight(x,y)$

int bfs(int u)
{
    tail=head=0;que[0]=u;
    pre[u]=0;
    while(tail<=head){
        u=que[tail++];vis[u]=1;
        for(reg int i=num_left+1;i<=num_right;i++)if(G[u][i]==lw[u]+lw[i]){
            if(vis[i]) continue;
            vis[i]=1;
            if(match[i]) pre[match[i]]=u,que[++head]=match[i];
            else{
                int d=u,e=i,t;
                while(d){
                    t=match[d];
                    match[d]=e;match[e]=d;
                    d=pre[d];e=t; 
                }
                return 1;
            }
        }
    }
    return 0;
}
long long max_match()
{
    for(reg int i=1;i<=num_left;i++){
        for(reg int j=num_left+1;j<=num_right;j++) lw[i]=std::max(lw[i],G[i][j]);
    }
    for(reg int i=1;i<=num_left;i++){
        std::memset(vis,0,sizeof vis);std::memset(d,0x7f,sizeof d);
        if(bfs(i)) continue;
        for(reg int j=1;j<=num_left;j++)if(vis[j])
            for(reg int k=num_left+1;k<=num_right;k++)
                if(!vis[k]) d[k]=std::min(d[k],lw[j]+lw[k]-G[j][k]);
        while(1){
            int now=1e9,to,s;
            for(reg int j=num_left+1;j<=num_right;j++)if(!vis[j]) now=std::min(now,d[j]);
            for(reg int j=1;j<=num_left;j++)if(vis[j]) lw[j]-=now;
            for(reg int j=num_left+1;j<=num_right;j++)
                if(vis[j]) lw[j]+=now; 
                else d[j]-=now,to=d[j]?to:j;
            if(!match[to]) break;
            s=match[to];vis[to]=vis[s]=1;
            for(reg int j=num_left+1;j<=num_right;j++)
                if(!vis[j]) d[j]=std::min(d[j],lw[s]+lw[j]-G[s][j]);
        }
        std::memset(vis,0,sizeof vis);
        bfs(i);
    }
    LL ans=0;
    for(reg int i=1;i<=num_right;i++) ans+=lw[i]; 
    return ans; 
}
int main()
{
    num_left=read();num_right=read();m=read();
    int nnn=num_left;
    num_left=num_right=std::max(num_left,num_right);
    num_right+=num_left;
    for(reg int u,v,i=1;i<=m;i++){
        u=read();v=read()+num_left;G[u][v]=G[v][u]=read();
    }
    std::printf("%lld\n",max_match());
    for(reg int i=1;i<=nnn;i++)
        std::printf("%d ",(match[i]&&G[i][match[i]])?(match[i]-num_left):0);
    return 0;
}

解题记录