二分图与匈牙利算法

· · 算法·理论

二分图

前置知识

难度

题目难度大多数都集中于 \color{#52C41A}{普及+/提高}

引入

二分图 (Bipartite Graph,偶图,二部图) 是一种特殊的图,可以将图中的结点分成两个不相交的集合 (A,B),每个集合中的结点都只会与不属于该集合中的结点连接,而不会与自己所在集合中的结点连接。

定义

前方高能,将会有大量知识点

性质

  1. 如果将二分图的每个结点染色,只需要两种颜色即可

  2. 二分图中,不包含奇数长度的环。

如何判断二分图

思路

只需要使用二分图的第一条性质即可,我们直接使用搜索跑一边图,边跑边染色,如果有图无法染色,就可以知道这张图一定不是二分图了。

代码实现

::::info[代码]

#include<bits/stdc++.h>
using namespace std;
const int kN = 1e5 + 7;
int n, m;
int dis[kN];
bool vis[kN];
vector<int>g[kN];
void Clear(){                     //清空
  for(int i = 1; i <= n; ++i){
    g[i].clear();
  }
  fill(dis + 1, dis + 1 + n, 0);
  fill(vis + 1, vis + 1 + n, 0);
  return ;
}
bool dfs(int s){
  vis[s] = 1;
  for(auto x: g[s]){
    if(vis[x]){
      if(dis[s] == dis[x]){
        return 0;
      }
    }else{
      dis[x] = dis[s] ^ 1;
      if(!dfs(x)){
        return 0;
      }
    }
  }
  return 1;
}
bool check(){
  for(int i = 1; i <= n; ++i){ //枚举每一个点
    if(!vis[i]){
      dis[i] = 0;
      if(!dfs(i)){
        return 0;
      }
    }
  }
  return 1;
}
void solve(){
  cin >> n >> m;
  for(int i = 1; i <= m; ++i){
    int u, v;
    cin >> u >> v;
    g[u].push_back(v);
    g[v].push_back(u);
  }
  bool flag = check();
  if(flag){
    cout << "Yes\n";
  }else{
    cout << "No\n";
  }
  return ;
}
signed main(){
  cin.tie(0) -> ios::sync_with_stdio(0);
  int T;
  cin >> T;
  while(T--){
    solve();
    Clear();
  }
  return 0;
}

::::

时间复杂度

#### 例题 - [T566836 【模板】二分图判定](https://www.luogu.com.cn/problem/T566836) ## 匈牙利算法 学之前,我们先要知道一些**增广路**的特征: - **非匹配边比匹配边一定多一条**。 因此,我们就可以利用这个特征来优化匹配,从而获得最大匹配,这个过程,就是匈牙利算法在做的事。 ### 如何通过增广路的特征来优化匹配 我们可以发现,如果我们将一条增广路上的匹配边与未匹配边交互,可以惊奇的发现: - 匹配边多了一条。 - 应为增广路不与别的边连接,所以交换后完全没有影响。 有了这个重要的东西,我们就可以通过在图中疯狂找增广路,再通过交换边,来增加匹配边,从而找到最大匹配。 ### 算法实现 不断为左侧集合中尚未匹配的点寻找一条增广路,即从该点出发,通过 DFS 探索一条起点和终点均为未匹配点、且匹配边与非匹配边交替出现的路径。如果找到这样的路径,就沿路径翻转匹配关系,使整体匹配数增加 $1$。重复这一过程,直到所有未匹配点都无法再找到增广路为止,最终得到最大匹配。 ### 代码实现 ::::info[代码] ```cpp #include<bits/stdc++.h> #define int long long using namespace std; const int kN = 5e4 + 7; int n, m, e, ans; vector<int>g[kN]; int dis[kN], match[kN]; bool dfs(int u, int sum){ if(dis[u] == sum){ return 0; } dis[u] = sum; for(auto v: g[u]){ if(match[v] == 0 || dfs(match[v], sum)){ match[v] = u; return 1; } } return 0; } signed main(){ cin >> n >> m >> e; for(int i = 1; i <= e; ++i){ int u, v; cin >> u >> v; g[u].push_back(v); } for(int i = 1; i <= n; ++i){ ans += dfs(i, i); } cout << ans; return 0; } ``` :::: ### 时间复杂度 $O(n ^ 2)$。 ### 例题 - [P3386 【模板】二分图最大匹配](https://www.luogu.com.cn/problem/P3386) ### 练习题 - [P1894 [USACO4.2] 完美的牛栏The Perfect Stall](https://www.luogu.com.cn/problem/P1894) - [P10937 車的放置](https://www.luogu.com.cn/problem/P10937) ## 声明 > 文中部分图片来源于网络。 - 如有不足,欢迎提出。