二分图匹配灵活运用

增广路思想的运用

构造二分图最小覆盖点方案

  UVa11419 SAM I AM
  求出最大匹配后,从所有X点集的未匹配点出发做dfs扩展匈牙利树,标记树中的所有点,则X中的未标记点和Y中的已标记点组成了所求的最小覆盖点集。

朴素的增广路算法也可有奇效

  P4382 [八省联考 2018] 劈配
  这题本质是二分图多重匹配,可以用网络流求解:按志愿等级优先的方式枚举选手当前志愿能否达成,可以用动态增删边的做法,但直接重新建图(先复制之前选手的网络流结果,再添加本次枚举需要的边)会更新高效,求解第二问用二分会更高效。
  但是用朴素的增广路算法来解反而是最优的,并且不需要什么建模技巧。还是按志愿等级优先的方式枚举选手当前志愿能否达成,增广过程种保持之前选手的志愿达成等级不变即可,并且增广过程种记录一下使选手不沮丧的志愿中增广到的学员中编号最大的那个,即是第二问答案,连二分都不需要。

/**
 * 洛谷 P4382 [八省联考 2018] 劈配
 */

#include <iostream>
#include <cstring>
using namespace std;

#define C 11
#define N 202
int w[N][N][C], c[N][N], py[N][N], px[N], rx[N], rk[N], b[N], t[N], s[N], m, n; bool vis[N];

bool match(int i, int j, int s) {
    if (i < s) rk[s] = max(rk[s], i);
    for (int k=0, d; k < c[i][j]; ++k) {
        if (vis[d = w[i][j][k]]) continue;
        vis[d] = true;
        if (t[d] < b[d]) {
            px[i] = d; rx[i] = j; py[d][t[d]++] = i;
            return true;
        }
        for (int p=0; p < b[d]; ++p) if (match(py[d][p], rx[py[d][p]], s)) {
            px[i] = j; rx[i] = j; py[d][p] = i;
            return true;
        }
    }
    return false;
}

void solve() {
    cin >> n >> m; memset(c, 0, sizeof(c));
    for (int i=1; i<=m; ++i) cin >> b[i], t[i] = 0;
    for (int i=1; i<=n; ++i) {
        px[i] = -1; rx[i] = m+1;
        for (int j=1; j<=m; ++j) {
            int a; cin >> a;
            if (a) w[i][a][c[i][a]++] = j;
        }
    }
    for (int i=1; i<=n; ++i) cin >> s[i];
    for (int i=1; i<=n; ++i) {
        memset(vis, rk[i] = 0, sizeof(vis));
        for (int j=1; j<=m; ++j) if (match(i, j, j<=s[i] ? i : 0)) {
            if (j <= s[i]) rk[i] = i;
            break;
        }
        if (i > 1) cout << ' ';
        cout << rx[i];
    }
    cout << endl;
    for (int i=1; i<=n; ++i) {
        if (i > 1) cout << ' ';
        cout << i - rk[i];
    }
    cout << endl;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int t, _; cin >> t >> _;
    while (t--) solve();
    return 0;
}

一些可转化为二分图最大权匹配的好题

  UVa1006/LA2238 Fixed Partition Memory Management

  洛谷 P2053 [SCOI2007] 修车,洛谷上本题还有一个增强版(洛谷P2050 [NOI2012] 美食节),需要跑费用流的过程中动态加点连边才能过。

二分图无奇环性质的运用

  UVa1364/LA3523 Knights of the Round Table

二分图最大匹配残量网络的强连通分量的应用

  求出二分图的最大匹配后,对其残量网络跑Tarjan算法求出所有的SCC后,可得出原二分图上哪些边是最大匹配的必须边,哪些是可行边。
  对残量网络求SCC的建图方法:新增源点s、汇点t;对X点集的匹配点 x i x_i xi,连边 x i → s x_i\rightarrow s xis,非匹配点 x j x_j xj连边 s → x j s\rightarrow x_j sxj;对Y点集的匹配点 y i y_i yi,连边 t → y i t\rightarrow y_i tyi,非匹配点 y j y_j yj连边 y j → t y_j\rightarrow t yjt;对匹配边 ( x i , y j ) (x_i,y_j) (xi,yj)连反向边 y j → x i y_j\rightarrow x_i yjxi,对非匹配边 ( x i , y j ) (x_i,y_j) (xi,yj)连正向边 x i → y j x_i\rightarrow y_j xiyj
  结论:对匹配边 ( x i , y j ) (x_i,y_j) (xi,yj),当且仅当 x i x_i xi y j y_j yj在不同的SCC时是必须边;对原二分图的边 ( x i , y j ) (x_i,y_j) (xi,yj),当 x i x_i xi y j y_j yj在同一个SCC时是可行边。

求二分图最大匹配的可行边

  UVa1327/LA2966 King’s Quest
  本题由于存在完美匹配,求SCC建图其实不需要源点和汇点(去掉和源点汇点相连的边)。

求二分图最大匹配的必须边

  P3731 [HAOI2017] 新型城市化
  本题需要一通分析才知道是求二分图最大匹配的必须边,依据最大独立集=总点数-最小覆盖最小覆盖=最大匹配

#include <iostream>
#include <cstring>
#include <vector>
using namespace std;

#define N 10002
int cc[N], dx[N], dy[N], p[N], vis[N], q[N], clk, d, m, n, t; vector<int> g[N];

bool bipartite(int u) {
    for (int i=g[u].size()-1, v; i>=0; --i) {
        if (cc[v = g[u][i]] == cc[u]) return false;
        if (!cc[v]) {
            cc[v] = 3-cc[u];
            if (!bipartite(v)) return false;
        }
    }
    return true;
}

bool search() {
    memset(dx, -1, sizeof(dx)); memset(dy, -1, sizeof(dy)); d = N;
    int head = 0, tail = 0;
    for (int i=1; i<=n; ++i) if (cc[i] == 1 && p[i] < 0) q[tail++] = i, dx[i] = 0;
    while (head < tail) {
        int u = q[head++];
        if (dx[u] > d) break;
        for (int i=g[u].size()-1, v; i>=0; --i) if (dy[v = g[u][i]] < 0) {
            dy[v] = dx[u] + 1;
            p[v] < 0 ? d = dy[v] : (dx[p[v]] = dy[v] + 1, q[tail++] = p[v]);
        }
    }
    return d != N;
}

bool dfs(int u) {
    for (int i=g[u].size()-1, v; i>=0; --i) if (vis[v = g[u][i]] != clk && dy[v] == dx[u]+1) {
        vis[v] = clk;
        if (p[v] >= 0 && dy[v] == d) continue;
        if (p[v] < 0 || dfs(p[v])) {
            p[u] = v; p[v] = u;
            return true;
        }
    }
    return false;
}

void max_match() {
    memset(p, -1, sizeof(p)); memset(vis, -1, sizeof(vis));
    for (clk=0; search(); ++clk) for (int i=1; i<=n; ++i) if (cc[i] == 1 && p[i] < 0) dfs(i);
}

int tarjan(int u) {
    int low = dx[u] = ++clk; q[d++] = u;
    for (int i=g[u].size()-1; i>=0; --i) {
        int v = g[u][i];
        if ((cc[u] == 1 && p[u] == v) || (cc[u] == 2 && v != t && p[u] != v)) continue;
        if (!dx[v]) low = min(low, tarjan(v));
        else if (!dy[v]) low = min(low, dx[v]);
    }
    if (low == dx[u]) {
        ++m;
        while (true) {
            dy[q[--d]] = m;
            if (q[d] == u) break;
        }
    }
    return low;
}

void solve() {
    if (m == 0) {
        cout << 0 << endl;
        return;
    }
    t = n+1; memset(cc, 0, sizeof(cc));
    for (int i=0; i<=t; ++i) g[i].clear();
    while (m--) {
        int u, v; cin >> u >> v; g[u].push_back(v); g[v].push_back(u);
    }
    for (int i=1; i<=n; ++i) if (!cc[i]) cc[i] = 1, bipartite(i);
    max_match(); memset(dx, clk = 0, sizeof(dx)); memset(dy, m = d = 0, sizeof(dy));
    for (int i=1; i<=n; ++i)
        if (cc[i] == 1) p[i] > 0 ? g[i].push_back(0) : g[0].push_back(i);
        else p[i] > 0 ? g[t].push_back(i) : g[i].push_back(t);
    for (int i=0; i<=t; ++i) if (!dx[i]) tarjan(i);
    d = 0;
    for (int i=1; i<=n; ++i) if (p[i] > i && dy[i] != dy[p[i]]) ++d;
    cout << d << endl;
    for (int i=1; i<=n; ++i) if (p[i] > i && dy[i] != dy[p[i]]) cout << i << ' ' << p[i] << endl;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while (cin >> n >> m) solve();
    return 0;
}

求二分图字典序第k最大匹配方案

  UVa1459/LA4748 Flowers Placement
  求出一个最大匹配后,dfs枚举剪枝。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值