首页 > 其他分享 >二分图匹配——从入门到入土

二分图匹配——从入门到入土

时间:2024-07-04 16:58:06浏览次数:11  
标签:二分 return 入门 增广 int DFS vis 入土 Dinic

基础知识

形式化定义:

二分图:\(G = (L, R, E)\),满足 \(\forall (u, v) \in E\) 都有 \(u \in L, v \in R\) 或 \(u \in R, v \in L\)。

可知“图中没有长度为奇数的环”是这个图是二分图的充分必要条件。

图的匹配是一个 \(E_m \subseteq E\) 满足 \(\nexists i \in V(\text{当} G \text{是二分图时} V = L \cup R)\) 满足 \(\exists (u, v), (w, x) \in E_m\),\([u = i] + [v = i] + [w = i] + [x = i] > 1\)。

图的极大匹配是一个 \(E_{M} \subseteq E\) 满足 \(\nexists i \in E \ \backslash \ E_M\) 满足 \(E_M \cup i\) 也是一个匹配。

二分图最大匹配

匈牙利算法(KM 算法)

这个算法很简单。

每一次找到二分图上一个 \(u \in L\) 到 \(v \in R\) 并且 \(v\) 没有被匹配的路径,把答案加一。

在找路的过程中搜出来的一棵树叫交错树,树上从 \(u\) 开始的路径叫一条交错路,成功找到的;一条路径叫增广路。

有一种 Dinic 的感觉,但是图没分层 Dinic 假了

单次增广时间复杂度 $O(|V| + |E|)$

很简单。

每次增广的时候最坏会遍历整张图,于是时间复杂度就是 \(O(|V| + |E|)\)。

总共会增广 $O(|V|)$ 轮

还是很简单。

每一次找到一条增广路时答案加一,答案是 \(O(|V|)\) 的,所以增广轮数就是 \(O(|V|)\) 的。

把这两个东西乘起来,总共的时间复杂度是 \(O(|V|(|V| + |E|))\) 的。

#include <bits/stdc++.h>
using namespace std;

int n, m, k, t[550], a, b, r[550], id[550], ans, vis[550];
vector<int> to[550];

int DFS(int x) {
  if(vis[x]) {
    return 0;
  }
  vis[x] = 1;
  for(auto i : to[x]) {
    if(!r[i]) {
      r[i] = x;
      t[x] = i;
      return 1;
    }
    if(DFS(r[i])) {
      r[i] = x;
      t[x] = i;
      return 1;
    }
  }
  return 0;
}

void KM() {
  for(int i = 1; i <= n; i++) {
    fill(vis + 1, vis + n + 1, 0);
    ans += DFS(i);
  }
}

int main() {
  for(cin >> n >> m >> k; k--; ) {
    cin >> a >> b;
    to[a].push_back(b);
  }
  KM();
  cout << ans << '\n';
  for(int i = 1; i <= n; i++) {
    if(t[i]) {
      cout << i << ' ' << t[i] << '\n';
    }
  }
  return 0;
}

Hopcroft-Karp 算法

Dinic 永远的神

其实跟 KM 算法几乎一模一样,只改了一个地方:每一次只增广最短的增广路。

欸?听起来跟 Dinic 很像?

我们比较一下 Dinic 与 Hopcroft-Karp 的流程 买一送一

Dinic
  1. BFS 出层次图;

  2. DFS 出阻塞流(层次图的最大流);

  3. 把阻塞流合并;

  4. 更新残量网络。

Hopcroft-Karp
  1. BFS 出层次图;

  2. DFS 出这时候的额外匹配;

  3. 合并匹配;

  4. 更新图。

这俩不是一样的吗?

对,对,就是一样的。

所以根据 Dinic 在这种特殊图上的时间复杂度,我们得到——

这个算法的时间复杂度是 \(O(|E| \min(|E|^{\frac{1}{2}}, |V|^{\frac{1}{2}}))\) 的。

稠密图上 \(|E| = O(|V|^2)\),所以是大概 \(O(|V|^3)\) 的;

稀疏图上 \(|E| = O(|V|)\),所以是大概 \(O(|E| |V|^{\frac{1}{2}})\) 的。

只有 \(|V| = 2000, |E| = 4 \times 10^6\) 才能卡掉。

而且Dinic 是“天选之子”时间跑不满......

#include <bits/stdc++.h>
using namespace std;

int n, m, k, t[100010], a, b, r[100010], id[100010], ans, vis[100010], dis[100010];
vector<int> to[100010];
queue<int> q;

//DFS 出额外匹配
int DFS(int x) {
  if(vis[x]) {
    return 0;
  }
  vis[x] = 1;
  for(auto i : to[x]) {
    //更新图
    if(!r[i]) {
      r[i] = x;
      t[x] = i;
      return 1;
    }
    if(dis[r[i]] == dis[x] + 1 && DFS(r[i])) {
      r[i] = x;
      t[x] = i;
      return 1;
    }
  }
  return 0;
}

//BFS 出层次图
int BFS() {
  fill(vis + 1, vis + n + 1, 0);
  fill(dis + 1, dis + n + 1, 0);
  for(int i = 1; i <= n; i++) {
    if(!t[i]) {
      q.push(i);
      dis[i] = 1;
    }
  }
  int f = 0;
  for(; q.size(); q.pop()) {
    int tmp = q.front();
    for(auto i : to[tmp]) {
      if(!r[i]) {
        f = 1;
      }
      if(r[i]) {
        if(!dis[r[i]]) {
          dis[r[i]] = dis[tmp] + 1;
          q.push(r[i]);
        }
      }
    }
  }
  return f;
}

void dinic() {
  for(; BFS(); ) {
    // 合并匹配
    for(int i = 1; i <= n; i++) {
      if((!t[i]) && DFS(i)) {
        ans++;
      }
    }
  }
}

int main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  for(cin >> n >> m >> k; k--; ) {
    cin >> a >> b;
    to[a].push_back(b);
  }
  cout << ans << '\n';
  for(int i = 1; i <= n; i++) {
    if(t[i]) {
      cout << i << ' ' << t[i] << '\n';
    }
  }
  return 0;
}

标签:二分,return,入门,增广,int,DFS,vis,入土,Dinic
From: https://www.cnblogs.com/leavenothingafterblog/p/18284051

相关文章

  • 都2024年了,还在问网络安全怎么入门,气得我当场脑血栓发作
    前言本人从事网路安全工作12年,曾在2个大厂工作过,安全服务、售后服务、售前、攻防比赛、安全讲师、销售经理等职位都做过,对这个行业了解比较全面。下面就开始进入正题,如何从一个萌新一步一步进入网络安全行业。正题首先,在准备进入这个行业之前,我们要问一下我们的内心,工作千......
  • 都2024年了,还在问网络安全怎么入门,气得我当场脑血栓发作
    前言本人从事网路安全工作12年,曾在2个大厂工作过,安全服务、售后服务、售前、攻防比赛、安全讲师、销售经理等职位都做过,对这个行业了解比较全面。下面就开始进入正题,如何从一个萌新一步一步进入网络安全行业。正题首先,在准备进入这个行业之前,我们要问一下我们的内心,工作千......
  • 都2024年了,还在问网络安全怎么入门,气得我当场脑血栓发作
    前言本人从事网路安全工作12年,曾在2个大厂工作过,安全服务、售后服务、售前、攻防比赛、安全讲师、销售经理等职位都做过,对这个行业了解比较全面。下面就开始进入正题,如何从一个萌新一步一步进入网络安全行业。正题首先,在准备进入这个行业之前,我们要问一下我们的内心,工作千......
  • 黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了
    想要成为黑客,却苦于没有方向,不知道从何学起,下面这篇黑客入门教程可以帮你实现自己的黑客梦想,如果想学,可以继续看下去,文章有点长,希望你可以耐心看到最后 1、Web安全相关概念(2周)·熟悉基本概念(SQL注入、上传、XSS、、CSRF、一句话木马等)。通过关键字(SOL注入、上传、XSSC......
  • 黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了
    想要成为黑客,却苦于没有方向,不知道从何学起,下面这篇黑客入门教程可以帮你实现自己的黑客梦想,如果想学,可以继续看下去,文章有点长,希望你可以耐心看到最后 1、Web安全相关概念(2周)·熟悉基本概念(SQL注入、上传、XSS、、CSRF、一句话木马等)。通过关键字(SOL注入、上传、XSSC......
  • 黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了
    想要成为黑客,却苦于没有方向,不知道从何学起,下面这篇黑客入门教程可以帮你实现自己的黑客梦想,如果想学,可以继续看下去,文章有点长,希望你可以耐心看到最后 1、Web安全相关概念(2周)·熟悉基本概念(SQL注入、上传、XSS、、CSRF、一句话木马等)。通过关键字(SOL注入、上传、XSSC......
  • Salesforce开发入门指南:零基础学习宝典!
    开发人员将Salesforce组织扩展到声明式配置之外,构建应用程序,进而优化业务运营。Salesforce开发人员通常会使用两种编程语言:Apex和JavaScript。然而,Salesforce开发不仅仅只包括代码。为了在职业道路上脱颖而出,开发人员还需要了解声明性功能,将组织的设计和性能保持最佳状态。Sal......
  • 使用 EFCore简单入门(实体类生成数据库表)
    1.安装Nuget包Microsoft.EntityFrameworkCore.SqlServerMicrosoft.EntityFrameworkCore.Tools2.创建Book,Post两个实体类publicclassBook{///<summary>///id///</summary>publicintId{get;set;}///<summary>///......
  • 2024Faceboo 商城自然流(从入门到精通),玩转脸书商城全闭环(教程+资料)
    摘要:本文旨在为读者提供一个全面的Facebook商城操作指南,从基础知识到高级应用技巧,帮助用户深入理解并有效利用Facebook商城进行跨境电商活动。1.引言介绍Facebook商城的发展历程及其在全球电商领域的影响力。2.Facebook商城概述2.1Facebook平台简介2.2Facebook商城的......
  • JAVA多线程快速入门
    什么是多线程概述线程线程是操作系统能够进行运算调度的最小单位它被包含在进程之中,是进程中的实际运作单位简单理解应用软件中互相独立,可以同时运行的功能进程进程是程序的基本执行实体/系统分配资源的基本单位作用充分利用cpu提......