二分圖及其最大匹配
概念
二分圖定義:二分圖,就是頂點集V可分割為兩個互不相交的子集,並且圖中每條邊依附的兩個頂點都分屬於這兩個互不相交的子集,兩個子集內的頂點不相鄰。如下圖就是一個二分圖:
注意:二分圖不一定是連通圖。
判定定理:一個圖是二分圖當且僅當沒有長度為奇數的圈。
染色法判定二分圖
從上面的判定定理可知, 我們可以通過染色法判斷有沒有奇圈。模板題點我,代碼如下:
//DFS求解
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
struct edge{
int nex, to;
}Edge[maxn<<1];
int n, m, head[maxn], co[maxn], tot;
inline void add(int from, int to){
Edge[++tot].to = to;
Edge[tot].nex = head[from];
head[from] = tot;
}
bool dfs(int u, int x){
co[u] = x;
for(int i = head[u]; i != -1; i = Edge[i].nex){
int v = Edge[i].to;
if(co[v] == 0){
if(!dfs(v, 3 - co[u])) return 0;
}
else if(co[v] == co[u]) return 0;
else if(co[v] + co[u] == 3) continue;
}
return 1;
}
int main()
{
scanf("%d %d", &n, &m);
memset(head, -1, sizeof(head));
for(int i = 1; i <= m; ++i){
int a, b;
scanf("%d %d", &a, &b);
add(a, b); add(b, a);
}
int ok = 1;
for(int i = 1; i <= n; ++i){
if(co[i]) continue;
if(dfs(i, 1)) continue;
ok = 0;
break;
}
ok ? puts("Yes") : puts("No");
system("pause");
}
//BFS求解
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
queue<int> q;
struct edge{
int nex, to;
}Edge[maxn<<1];
int n, m, head[maxn], co[maxn], tot;
inline void add(int from, int to){
Edge[++tot].to = to;
Edge[tot].nex = head[from];
head[from] = tot;
}
bool bfs(int x){
co[x] = 1;
q.push(x);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = Edge[i].nex){
int v = Edge[i].to;
if(co[v] == co[u]) return 0;
else if(co[v] == 0){
co[v] = 3 - co[u];
q.push(v);
}
else if(co[u] + co[v] == 3) continue;
}
}
return 1;
}
int main()
{
scanf("%d %d", &n, &m);
memset(head, -1, sizeof(head));
for(int i = 1; i <= m; ++i){
int a, b;
scanf("%d %d", &a, &b);
add(a, b); add(b, a);
}
int ok = 1;
for(int i = 1; i <= n; ++i){
if(co[i]) continue;
if(bfs(i)) continue;
ok = 0;
break;
}
ok ? puts("Yes") : puts("No");
system("pause");
}
二分圖最大匹配
基本概念:
匹配:“任意兩條邊都沒有公共端點”的邊的集合稱為匹配。
最大匹配:包含邊的個數最多的一組匹配。
增廣路:一個連接兩個非匹配點的路徑path, 使得非匹配邊和匹配邊在path上交替出現(第一條邊和最后一條邊都是未匹配邊),稱path是一個增廣路。
求解二分圖的最大匹配問題可用匈牙利算法(增廣路算法)。下面給出模板代碼:
模板題
【題意】:求解最大匹配的邊數。時間復雜度為\(O(nm)\)。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
struct edge{
int nex, to;
}Edge[maxn * maxn];
int n, m, e;
int head[maxn], tot;
int match[maxn], vis[maxn], ans;
//vis表示每次搜索有沒有被訪問到, match表示點是不是匹配點
inline void add(int from, int to){
Edge[++tot].to = to;
Edge[tot].nex = head[from];
head[from] = tot;
}
bool dfs(int u)
{
for(int i = head[u]; i != -1; i = Edge[i].nex){
int v = Edge[i].to;
if(vis[v]) continue;
vis[v] = 1;
if(!match[v] || dfs(match[v])){
match[v] = u;
return 1;
}
}
return 0;
}
int main()
{
scanf("%d %d %d", &n, &m, &e);
memset(head, -1, sizeof(head));
for(int i = 1; i <=e; ++i){
int u, v;
scanf("%d %d", &u, &v);
if(u > n || v > m) continue;
add(u, v);
}
for(int i = 1; i <= n; ++i){
memset(vis, 0, sizeof(vis));
if(dfs(i)) ans++;
}
printf("%d\n", ans);
system("pause");
}
在將一個問題轉化為二分圖匹配模型時, 要注意兩個要素:
0要素:節點能分成兩個獨立的集合, 每個集合內部有0條邊
1要素:每個節點只能與1條匹配邊相連
棋盤覆蓋
【描述】:有n * n的棋盤, 輸入一些坐標, 表示這些坐標中不能有1 * 2 的方塊覆蓋它。問這個棋盤上最多能放多少個1 * 2 的棋盤。
【思路】:由於是1*2的方塊, 即一個方塊連接另一個相鄰的方塊,這里可以看做是一個二分圖模型。由於坐標和全為奇數的集合中任意兩點不可能被同一個方塊覆蓋。所以我們可以將所有坐標和為奇數的點的集合去匹配坐標和為偶數的集合,求一下最大匹配即可。時間復雜度\(O(n^2m^2)\)。
0要素:坐標和為奇數的點和坐標和為偶數的點兩個集合,且集合中沒有邊。
1要素:相鄰坐標的坐標和奇偶性不同, 且只能連接一條邊。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e4 + 10;
const int inf = 0x3f3f3f3f;
int gcd(int a,int b) { return b == 0 ? a : gcd(b, a % b); }
struct edge{
int nex, to;
}Edge[maxn<<2];
int n, t, m[110][110];
int head[maxn], tot;
int vis[maxn], match[maxn];
int ans;
inline void add(int from, int to){
Edge[++tot].to = to;
Edge[tot].nex = head[from];
head[from] = tot;
}
inline int id(int x, int y) { return y + (x - 1) * n; }
bool dfs(int u)
{
for(int i = head[u]; i != -1; i = Edge[i].nex){
int v = Edge[i].to;
if(vis[v]) continue;
vis[v] = 1;
if(!match[v] || dfs(match[v])){
match[v] = u;
return 1;
}
}
return 0;
}
int main()
{
scanf("%d %d", &n, &t);
memset(head, -1, sizeof(head));
while(t--){
int a, b;
scanf("%d %d", &a, &b);
m[a][b] = 1;
}
//奇數點連偶數點
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j){
if((i + j) % 2 == 0) continue;
if(m[i][j]) continue;
if(i > 1 && !m[i-1][j]) add(id(i, j), id(i-1, j));
if(i < n && !m[i+1][j]) add(id(i, j), id(i+1, j));
if(j > 1 && !m[i][j-1]) add(id(i, j), id(i, j-1));
if(j < n && !m[i][j+1]) add(id(i, j), id(i, j+1));
}
}
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j){
if((i + j) % 2 == 0) continue;
for(int k = 1; k <= n*n; ++k) vis[k] = 0;
if(dfs(id(i, j))) ans++;
}
}
printf("%d\n", ans);
system("pause");
}
車的放置
【描述】:一個n * m的棋盤, 有的地方不能放車,問棋盤上最多能放多少個車,使得不相互攻擊。
【思路】:0要素:1車不可能同時屬於兩行或兩列。
1要素:一行與一列只能通過一個車匹配。
所以可以將所有的行當成1個集合, 所有的列當成一個集合,求一下最大匹配就好了。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 4e4 + 10;
const int inf = 0x3f3f3f3f;
int gcd(int a,int b) { return b == 0 ? a : gcd(b, a % b); }
struct edge{
int nex, to;
}Edge[maxn];
int head[maxn], tot;
int n, m, t, arr[210][210];
int vis[maxn], match[maxn];
inline void add(int from, int to){
Edge[++tot].to = to;
Edge[tot].nex = head[from];
head[from] = tot;
}
bool dfs(int u){
for(int i = head[u]; i != -1; i = Edge[i].nex){
int v = Edge[i].to;
if(vis[v]) continue;
vis[v] = 1;
if(!match[v] || dfs(match[v])){
match[v] = u;
return 1;
}
}
return 0;
}
int main()
{
scanf("%d %d %d", &n, &m, &t);
memset(head, -1, sizeof(head));
while(t--){
int u, v;
scanf("%d %d", &u, &v);
arr[u][v] = 1;
}
//行匹配列
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
if(arr[i][j]) continue;
add(i, j);
}
}
int ans = 0;
for(int i = 1; i <= n; ++i){
memset(vis, 0, sizeof(vis));
if(dfs(i)) ans++;
}
printf("%d\n", ans);
system("pause");
}
二分圖的完美匹配
完美匹配:和最大匹配相似, 在二分圖中,左右定點都為n, 如果能匹配到n條邊的話, 我們稱這個匹配是完美匹配。由此看出, 完美匹配一定是最大匹配, 最大匹配不一定是完美匹配。
二分圖多重匹配
多重匹配:即1個人左節點或右節點可以與多個右節點或左節點相連。這樣的匹配叫做多重匹配。
解法1:可以拆點, 把每一個左節點拆成\(kl_i\)個左節點, 將每一個右節點拆成\(kr_i\)個右結點,然后將它們分邊相連。很明顯, 當\(kl_i = kr_i = 1\)時, 就簡化成一個二分圖的最大匹配。
解法2:網絡流。
【題意】:n座防御塔, m個侵略者,一個防御塔發射炮彈需要\(t1 + dis(a[i], b[j]) / v\)個時間, 其中 t1 是炮彈發射時間, dis(a[i], b[j])是導彈從防御塔到怪獸之間飛的時間,一個防御塔發射第二枚炮彈時還需等待防御塔冷卻時間 t2,問最少要多少時間能把所有怪獸全部消滅。
【思路】:首先這個答案在一個區間內肯定存在, 所以我們可以二分答案,對於每個答案,我們用二分圖匹配, 看能不能匹配到 m 個邊,如果可以,那么對於小於等於它的答案也可以,否則答案大於它。在判定時, 由於怪獸只能被消滅一次,所以將怪獸作為二分圖左結點,對於防御塔,我們可以將一個防御塔看做發射 m 枚導彈的 m 個點, 將這些點分別於怪獸的點相連,注意判斷怪獸與第k枚防御塔之間消耗的時間, 不大於才能練邊。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int maxn = 55, maxm = 2e5 + 10;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8, pi = acos(-1);
int gcd(int a,int b) { return b == 0 ? a : gcd(b, a % b); }
struct node{
int x, y;
}a[110], b[110];
struct edge{
int nex, to;
}Edge[maxm];
int head[maxm], tot;
int n, m, v;
double t1, t2;
int vis[maxn*maxn], match[maxn*maxn];
inline double dis(node a, node b){
return sqrt(1.0 * (a.x - b.x) * (a.x - b.x) + 1.0 * (a.y - b.y) * (a.y - b.y));
}
inline int id(int x, int y) { return y + m * (x - 1); }
inline void add(int from, int to){
Edge[++tot].to = to;
Edge[tot].nex = head[from];
head[from] = tot;
}
bool dfs(int u)
{
for(int i = head[u]; i != -1; i = Edge[i].nex){
int v = Edge[i].to;
if(vis[v]) continue;
vis[v] = 1;
if(!match[v] || dfs(match[v])){
match[v] = u;
return 1;
}
}
return 0;
}
bool check(double x)
{
memset(head, -1, sizeof(head));
memset(match, 0, sizeof(match));
tot = 0;
for(int i = 1; i <= m; ++i){
for(int j = 1; j <= n; ++j){ //第j座防御塔的第k發炮彈
for(int k = 1; k <= m; ++k){
if(t1 * k + t2 * (k - 1) + dis(a[i], b[j]) / v > x) continue;
add(i, id(j, k));
}
}
}
int ans = 0;
for(int i = 1; i <= m; ++i){
memset(vis, 0, sizeof(vis));
if(dfs(i)) ans++;
}
return ans == m;
}
int main()
{
scanf("%d %d %lf %lf %d", &n, &m, &t1, &t2, &v);
t1 /= 60;
for(int i = 1; i <= m; ++i){
scanf("%d %d", &a[i].x, &a[i].y);
}
for(int i = 1; i <= n; ++i){
scanf("%d %d", &b[i].x, &b[i].y);
}
double l = 0, r = 2000000;
while(r - l > eps)
{
double mid = (l + r) / 2;
if(check(mid)) r = mid;
else l = mid;
}
printf("%.6lf\n", l);
system("pause");
}