二分图的匹配

二分图判定问题。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e4 + 50;
struct edge
{
    int v, cost;
};
vector<edge> g[maxn];
int vis[maxn];
int flag = 0;
void dfs(int u, int color, int mid)
{
    vis[u] = color;
    for(int i = 0; i < (int)g[u].size(); i++)
    {
        int v = g[u][i].v;
        if(g[u][i].cost > mid) ///这里必须是大于mid,代表可拆成二分图 我们求的就是恰好不能拆成二分图的mid
        {
            if(vis[v] == 0)
            {
                dfs(v, 3 - color, mid);
            }
            else if(vis[v] == color)
            {
                flag = 1;
                return;
            }
        }
    }
}
int check(int mid, int n)
{
    for(int i = 1; i <= n; i++) vis[i] = 0;
    flag = 0;
    for(int i = 1; i <= n; i++)
    {
        if(!vis[i]) dfs(i, 1, mid);
    }
    return (!flag);
}
int main()
{
    int n, m; scanf("%d %d", &n, &m);
    int l = 0, r = 0;
    for(int i = 1; i <= m; i++)
    {
        int u, v, w; scanf("%d %d %d", &u, &v, &w);
        g[u].push_back({v, w});
        g[v].push_back({u, w});
        r = max(r, w);
    }
    int ans = 0;
    while(l <= r)
    {
        int mid = (l + r) >> 1;
        if(check(mid, n))
        {
            ans = mid;
            r = mid - 1;
        }
        else
        {
            l = mid + 1;
        }
    }
    printf("%d\n", ans);
    return 0;
}
Code

 

棋盘覆盖

二分图匹配模型的两个要素:

“0元素”:节点分成独立的两个集合,每个集合内部有0条边。

“1元素”:每个节点最多只能与1条匹配边相连。

对于本题,可以把棋盘中相邻格子染成黑白相间,那么同一块骨牌不可能覆盖两个颜色一样的格子,集合内没有边。(0元素)

每个格子最多只能被1块骨牌覆盖,最多有一条边。(1元素)

二分图的最大匹配问题。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 50;
int ban[105][105];
vector<int> lepart;
int n;
int check(int x, int y)
{
    if(x >= 1 && x <= n && y >= 1 && y <= n && ban[x][y] == 0) return 1;
    else return 0;
}
vector<int> g[maxn];
int vis[maxn], match[maxn];
int dfs(int x)
{
    for(int i = 0; i < g[x].size(); i++)
    {
        int y = g[x][i];
        if(!vis[y])
        {
            vis[y] = 1;
            if(!match[y] || dfs(match[y]))
            {
                match[y] = x;
                return true;
            }
        }
    }
    return false;
}
void add(int u, int v)
{
    g[u].push_back(v);
    g[v].push_back(u);
}
int main()
{
    int t;
    scanf("%d %d", &n, &t);
    for(int i = 1; i <= t; i++)
    {
        int x, y; scanf("%d %d", &x, &y);
        ban[x][y] = 1;  ///     禁用了
    }
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            if((i + j) % 2 == 0 && ban[i][j] == 0)
            {
                int node = (i - 1) * n + j;
                lepart.push_back(node);
                if(check(i - 1, j)) add(node, node - n);
                if(check(i + 1, j)) add(node, node + n);
                if(check(i, j - 1)) add(node, node - 1);
                if(check(i, j + 1)) add(node, node + 1);
            }
        }
    }
    int ans = 0;
   // printf("%d\n", lepart.size());
    for(int i = 0; i < (int)lepart.size(); i++)
    {
        memset(vis, 0, sizeof(vis));
        if(dfs(lepart[i])) ans++;
    }
    printf("%d\n", ans);
}
Code

 

車的放置

因为每行每列至多只能有一个车,我们寻找两个元素:

“1元素”:每行每列至多有一个车,也就是最多有一条匹配边。

“0元素”:行与行之间不会有边相连。

 二分图的最大匹配问题。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 405;
int ban[205][205];
int n, m;
vector<int> g[maxn];
int vis[maxn], match[maxn];
int dfs(int x)
{
    for(int i = 0; i < g[x].size(); i++)
    {
        int y = g[x][i];
        if(!vis[y])
        {
            vis[y] = 1;
            if(!match[y] || dfs(match[y]))
            {
                match[y] = x;
                return true;
            }
        }
    }
    return false;
}
void add(int u, int v)
{
    g[u].push_back(v);
    g[v].push_back(u);
}
int main()
{
    int t;
    scanf("%d %d %d", &n, &m, &t);
    for(int i = 1; i <= t; i++)
    {
        int x, y; scanf("%d %d", &x, &y);
        ban[x][y] = 1;  ///     禁用了
    }
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            if(ban[i][j] == 0)
            {
                add(i, n + j);
            }
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; i++)
    {
        memset(vis, 0, sizeof(vis));
        if(dfs(i)) ans++;
    }
    printf("%d\n", ans);
}
Code

 

导弹防御塔

本题是二分图的多重匹配问题。

每个防御塔可以发射多枚导弹,但每个入侵者只要被一个打击,就能击毁,那每个入侵者只能与一个导弹连边。(“1元素”)

 题目问最少需要多少分钟才能击退所有入侵者,答案符合单调性,可以二分来做。

我们假设在$mid$时间内,每个防御塔能够发射$P$个导弹,第$i$个防御塔的第$k(1≤k≤P)$个防御塔能够击毁第$j$个入侵者,就在导弹和入侵者之间连一条边。

对入侵者跑匈牙利算法,若每个入侵者都能找到匹配,则此$mid$可行。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5;
vector<int> g[maxn];
int vis[maxn], match[maxn];
int dfs(int x)
{
    for(int i = 0; i < g[x].size(); i++)
    {
        int y = g[x][i];
        if(!vis[y])
        {
            vis[y] = 1;
            if(!match[y] || dfs(match[y]))
            {
                match[y] = x;
                return true;
            }
        }
    }
    return false;
}
void add(int u, int v)
{
    g[u].push_back(v);
    g[v].push_back(u);
}
#define x1 xx1
#define x2 xx2
#define y1 yy1
#define y2 yy2
int x1[55], y1[55], x2[55], y2[55];
int n, m, t1, t2, v;
double pf(double x)
{
    return x * x;
}
const double eps = 1e-8;
int check(double mid, double t1)
{
    int cnt = (mid + t2 + eps) / (t1 + t2);
    for(int i = 1; i <= n * cnt + m; i++) g[i].clear(), match[i] = 0;
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            double dis = sqrt(pf(x2[i] - x1[j]) + pf(y2[i] - y1[j]));
            double t = dis / v + t1;
            for(int k = 1; k <= cnt; k++)
            {
                if(t < mid || fabs(t - mid) <= eps)
                {
                    add((i - 1) * cnt + k, n * cnt + j);
                    t = t + t1 + t2;
                }
                else break;
            }
        }
    }
    int ans = 0;
    for(int i = cnt * n + 1; i <= n * cnt + m; i++)
    {
        for(int j = 1; j <= n * cnt + m; j++) vis[j] = 0;
        if(dfs(i)) ans++;
    }
    return ans >= m;
}
int main()
{
    scanf("%d %d %d %d %d", &n, &m, &t1, &t2, &v);
    double fuckminute = 1.0 * t1 / 60;
    for(int i = 1; i <= m; i++) scanf("%d %d", &x1[i], &y1[i]);
    for(int i = 1; i <= n; i++) scanf("%d %d", &x2[i], &y2[i]);
    double l = 0, r = 1e5;
    int cnt = 100;
    double ans = 0;
    while(cnt--)
    {
        double mid = (l + r) / 2;
        if(check(mid, fuckminute))
        {
            ans = mid;
            r = mid;
        }
        else l = mid;
    }
    printf("%.6f\n", ans);
}
Code

 

 POJ 3565 Ants

 本题是二分图带权最大匹配问题

 所有线段不相交,就是所有线段的长度之和最小。

 注意KM模板的正确性。

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn = 105;

int xx1[maxn], yy1[maxn];
int xx2[maxn], yy2[maxn];
double w[maxn][maxn];
double pf(int x)
{
    return (double)x * x;
}
double la[maxn], lb[maxn];
bool va[maxn], vb[maxn];
int match[maxn];
int n;
double delta;
const double eps = 1e-10;
bool dfs(int x)
{
    va[x] = 1; ///在交错树中
    for(int y = 1; y <= n; y++)
    {
        if(!vb[y])
        {
            if(fabs(la[x] + lb[y] - w[x][y]) <= eps)
            {
                vb[y] = 1;
                if(!match[y] || dfs(match[y]))
                {
                    match[y] = x;
                    return true;
                }
            }
        }
    }
    return false;
}
int ans[maxn];
int KM()
{
    for(int i = 1; i <= n; i++)
    {
        la[i] = -1e12;
        lb[i] = 0;
        for(int j = 1; j <= n; j++)
        {
            la[i] = max(la[i], w[i][j]);
        }
    }
    for(int i = 1; i <= n; i++)
    {
        while(1)
        {
            memset(va, 0, sizeof(va));
            memset(vb, 0, sizeof(vb));
            if(dfs(i)) break;
            delta = 1e9;
            for(int j = 1; j <= n; j++)
            {
                if(va[j])
                {
                    for(int k = 1; k <= n; k++)
                    {
                        if(!vb[k]) delta = min(delta, la[j] + lb[k] - w[j][k]);
                    }
                }
            }
            for(int j = 1; j <= n; j++)
            {
                if(va[j]) la[j] -= delta;
                if(vb[j]) lb[j] += delta;
            }
        }
    }
    for(int i = 1; i <= n; i++) ans[match[i]] = i;
}
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d %d", &xx1[i], &yy1[i]);
    }
    for(int i = 1; i <= n; i++)
    {
        scanf("%d %d", &xx2[i], &yy2[i]);
    }
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            w[i][j] = -sqrt(pf(xx1[i] - xx2[j]) + pf(yy1[i] - yy2[j]));
        }
    }
    KM();
    for(int i = 1; i <= n; i++)
    {
        printf("%d\n", ans[i]);
    }
    return 0;
}
Code

 

POJ 1325 Machine Schedule

本题是二分图最小点覆盖

模型特点是:“2要素”:每条边有2个端点,二者至少选择一个。

本题的2要素:每个任务要么在机器A上运行,要么在模式B上运行。

这题一开始就在模式0下,所有一开始就把和0相连的先做了。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cstdio>
using namespace std;
const int maxn = 205;

vector<int> g[maxn];
int vis[maxn], match[maxn];
int dfs(int x)
{
    vis[x] = 1;
    for(int i = 0; i < g[x].size(); i++)
    {
        int y = g[x][i];
        if(!vis[y])
        {
            vis[y] = 1;
            if(!match[y] || dfs(match[y]))
            {
                match[y] = x;
                return true;
            }
        }
    }
    return false;
}
void add(int u, int v)
{
    g[u].push_back(v);
    g[v].push_back(u);
}
int tmp[maxn];
int main()
{
    int n, m, k;
    while(scanf("%d", &n) != EOF && n)
    {
        scanf("%d %d", &m, &k);
        for(int i = 1; i <= n + m; i++)
        {
            g[i].clear();
            match[i] = 0;
            tmp[i] = 0;
        }
        for(int i = 1; i <= k; i++)
        {
            int ii, x, y; scanf("%d %d %d", &ii, &x, &y);
            x++, y++;
            if(x != 1 && y != 1) add(x, y + n);
        }
        for(int i = 1; i <= n; i++)
        {
            memset(vis, 0, sizeof(vis));
            dfs(i);
        }
        memset(vis, 0, sizeof(vis));
        for(int i = n + 1; i <= n + m; i++)
        {
            tmp[match[i]] = 1;
        }
        for(int i = 1; i <= n; i++)
        {
            if(!tmp[i]) dfs(i);
        }
        int ans = 0;
        for(int i = 1; i <= n; i++)
        {
            if(!vis[i]) ans++;
        }
        for(int i = n + 1; i <= n + m; i++)
        {
            if(vis[i]) ans++;
        }
        printf("%d\n", ans);
    }
    return 0;
}
Code

 

POJ 2226 Muddy Fields

本题也是二分图最小点覆盖问题。

寻找“2”要素,每个泥泞点要么被横着的木板挡住,要么被竖着的木板挡住,二者至少选择一个,这时候每个泥点相当于边。

 

#include <algorithm>
#include <cstring>
#include <vector>
#include <cstdio>
using namespace std;
const int maxn = 2505;

vector<int> g[maxn];
int vis[maxn], match[maxn];
int dfs(int x)
{
    vis[x] = 1;
    for(int i = 0; i < g[x].size(); i++)
    {
        int y = g[x][i];
        if(!vis[y])
        {
            vis[y] = 1;
            if(!match[y] || dfs(match[y]))
            {
                match[y] = x;
                return true;
            }
        }
    }
    return false;
}
void add(int u, int v)
{
    g[u].push_back(v);
    g[v].push_back(u);
}
char s[55][55];
int l[55][55], c[55][55];
int main()
{
    int n, m;
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%s", s[i] + 1);
    }
    int num1 = 0;
    memset(l, 0, sizeof(l));
    memset(c, 0, sizeof(c));
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            if(s[i][j] == '*')
            {
                if(!l[i][j - 1])
                {
                    l[i][j] = ++num1;
                }
                else l[i][j] = l[i][j - 1];
            }


        }
    }
    int num2 = num1;
    for(int j = 1; j <= m; j++)
    {
        for(int i = 1; i <= n; i++)
        {
            if(s[i][j] == '*')
            {
                if(!c[i - 1][j])
                {
                    c[i][j] = ++num2;
                }
                else c[i][j] = c[i - 1][j];
            }
        }
    }
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            if(s[i][j] == '*')
            {
                add(l[i][j], c[i][j]);
            }
        }
    }
    int ans = 0;
    for(int i = 1; i <= num1; i++)
    {
        memset(vis, 0, sizeof(vis));
        if(dfs(i)) ans++;
    }
    printf("%d\n", ans);
    return 0;
}
Code

 

posted @ 2018-10-08 21:12  汪汪鱼  阅读(222)  评论(0编辑  收藏  举报