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\times 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\n", L);
    }
    return 0;
}
View Code

 

posted @ 2019-08-11 23:55  _kangkang  阅读(256)  评论(0编辑  收藏  举报