zoukankan      html  css  js  c++  java
  • HDU-3081 Marriage Match II (最大流,二分答案,并查集)

    题目链接:HDU-3081 Marriage Match II

    题意

    有$n$个男孩和$n$个女孩玩配对游戏,每个女孩有一个可选男孩集合(即每轮游戏的搭档可从集合中选择),已知有些女孩之间是朋友(这里的朋友关系是相互的,即a和b是朋友,a和c是朋友,那么b和c也是朋友),那么她们可以共享男孩集合,即这些男孩集合的并集成为她们各自的可选男孩集合,如果某一轮女孩选择了一个男孩作为搭档,则这个男孩后面不能再作为这个女孩的搭档,问游戏最多进行几轮。


    思路

    女孩朋友之间共享男孩集合问题可以用并查集解决,将互为朋友的女孩放到一个并查集中,最后检查女孩两两之间是否在同一个并查集,是则说明她们是朋友,遍历$n$个男孩,合并她们与男孩之间的关系。

    假设当前二分尝试的轮数为$K$,源点为$s$,汇点为$t$,用$(始点,终点,容量)$表示边,建立流网络如下:

    若男孩$v$在女孩$u$的可选男孩集合中,连边$(u,v,1)$,表示女孩$u$只能选男孩$v$一次;

    对于每个女孩$u$,连边$(s,u,K)$,表示每个女孩需要选择$K$个男孩作为$K$轮的搭档;

    对于每个男孩$v$,连边$(v, t, K)$,表示每个男孩只能被$K$个女孩选到。

    如果这个流网络的最大流为$n imes K$,那么说明游戏进行$K$轮是可行的。

    为什么$s$的出边和$t$的入边流量都为$K$(将之简称为满流)就说明游戏进行$K$轮是可行的?

    实际上若$K>1$时满流的话说明$K=1$也是可以满流的,因为$K=1$满流是更弱的要求,$K=1$满流显然等价于可以进行一轮;如果$K=2$可以满流的话说明在$K=1$的流量流过之后(1轮),女孩男孩之间连边的残余容量可以再满足一次$K=1$的流量流过,即可以再进行一轮(2轮),以此类推得出$s$的出边和$t$的入边容量都为$K$时能满流的话,则游戏能进行$K$轮。那么我们不断二分尝试$K$,找到能满流的最大$K$,就是最大的游戏轮数。

    上面的说明并不是严谨的数学证明,谁会严谨证明欢迎交流。


    代码实现

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    using std::queue;
    const int INF = 0x3f3f3f3f, N = 220, M = 30000;
    int head[N], d[N], par[N], ht[N];
    int s, t, tot, maxflow;
    bool dist[N][N];
    struct Edge
    {
        int to, cap, nex;
    } edge[M];
    queue<int> q;
    void add(int x, int y, int z) {
        edge[++tot].to = y, edge[tot].cap = z, edge[tot].nex = head[x], head[x] = tot;
        edge[++tot].to = x, edge[tot].cap = 0, edge[tot].nex = head[y], head[y] = tot;
    }
    bool bfs() {
        memset(d, 0, sizeof(d));
        while (q.size()) q.pop();
        q.push(s); d[s] = 1;
        while (q.size()) {
            int x = q.front(); q.pop();
            for (int i = head[x]; i; i = edge[i].nex) {
                int v = edge[i].to;
                if (edge[i].cap && !d[v]) {
                    q.push(v);
                    d[v] = d[x] + 1;
                    if (v == t) return true;
                }
            }
        }
        return false;
    }
    int dinic(int x, int flow) {
        if (x == t) return flow;
        int rest = flow, k;
        for (int i = head[x]; i && rest; i = edge[i].nex) {
            int v = edge[i].to;
            if (edge[i].cap && d[v] == d[x] + 1) {
                k = dinic(v, std::min(rest, edge[i].cap));
                if (!k) d[v] = 0;
                edge[i].cap -= k;
                edge[i^1].cap += k;
                rest -= k;
            }
        }
        return flow - rest;
    }
    void init2() {
        tot = 1, maxflow = 0;
        memset(head, 0, sizeof(head));
    }
    void init1(int n) {
        for (int i = 0; i <= n + 1; i++) par[i] = i, ht[i] = 0;
        s = 0, t = n + 1;
        memset(dist, 0, sizeof(dist));
    }
    int Find(int x) {
        if (par[x] == x) return x;
        else return par[x] = Find(par[x]);
    }
    void unite(int x, int y) {
        x = Find(x), y = Find(y);
        if (x == y) return ;
        if (ht[x] < ht[y]) par[x] = y;
        else {
            par[y] = x;
            if (ht[x] == ht[y]) ht[x]++;
        }
    }
    bool solve(int n, int K) {
        init2();
        for (int i = 1; i <= n; i++) {
            for (int j = n + 1; j <= 2 * n; j++) {
                if (dist[i][j]) add(i, j, 1);
            }
            add(s, i, K);
        }
        for (int i = n + 1; i <= 2 * n; i++) add(i, t, K);
        while (bfs()) maxflow += dinic(s, INF);
    
        return (maxflow == n * K);
    }
    
    int main() {
        int T;
        scanf("%d", &T);
        while (T--) {
            int n, m, f;
            scanf("%d %d %d", &n, &m, &f);
            init1(2 * n);
            for (int i = 0, u, v; i < m; i++) {
                scanf("%d %d", &u, &v);
                dist[u][v+n] = true;
            }
            for (int i = 0, u, v; i < f; i++) {
                scanf("%d %d", &u, &v);
                unite(u, v);
            }
            for (int i = 1; i <= n; i++) {
                for (int j = i + 1; j <= n; j++) {
                    if (Find(i) == Find(j)) {
                        for (int k = n + 1; k <= 2 * n; k++) {
                            dist[i][k] = dist[j][k] = (dist[i][k] || dist[j][k]);
                        }
                    }
                }
            }
            int L = 0, R = 101;
            while (R - L > 1) {
                int mid = (L + R) >> 1;
                if (solve(n, mid)) L = mid;
                else R = mid;
            }
            printf("%d
    ", L);
        }
        return 0;
    }
    View Code
    作者:_kangkang
    本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
  • 相关阅读:
    2020中国最好大学排名.py(亲测有效)
    手机号码归属地的自动查询.py(亲测有效)
    网络图片的爬取和存储.py(亲测有效)
    百度360搜索关键词提交.py(亲测有效)
    亚马逊商品页面爬取(使用headers字段).py(亲测有效)
    淘宝商品页面的爬取.py(亲测有效)
    爬取网页的通用代码框架.py(亲测有效)
    匿名函数function前面的! ~等符号作用小解
    ng之自定义指令
    用nodejs搭建一个简单的服务监听程序
  • 原文地址:https://www.cnblogs.com/kangkang-/p/11337355.html
Copyright © 2011-2022 走看看