[Codeforces 976F] Minimal k-covering

本文介绍了一种解决特定二分图问题的算法——通过调整每个节点的覆盖次数至特定阈值来寻找最小边集,并使用最大流的概念来实现。算法采用朴素DFS方法求解最大流,适用于节点数量不大于2000的情况。

题目链接: http://codeforces.com/problemset/problem/976/F

题目大意: 给一个二分图, 可以有重边, 使得每个点覆盖次数至少为k(0~minDegree)的最小边集。 n,m2000 ( n , m ≤ 2000 )

题目思路: 所谓每个点覆盖次数至少为k, 可以看成流量至多为deg[i]-k, 然后跑最大流, 剩下的边就是我们选择的边。 我们从大往小求每个k的答案, 每次只要将一些边的流量+1, 接着上一次的残留网络上跑。 我们用最朴素的方法来求最大流, 每次dfs找增广路, 每次dfs只增广一次, 一共最多做m次dfs。

Code:

#include <cstdio>
#include <cstdlib>
#include <algorithm>

using namespace std;

#define ll long long

const int N = (int)2000 + 10;

int n1, n2, m, s, t;
int cnt = 1, lst[N * 2], to[N * 10], nxt[N * 10], f[N * 10];
int deg[N * 2], ans[N][N]; 
int indx, tag[N * 2];

void add(int u, int v, int flow){
    nxt[++ cnt] = lst[u]; lst[u] = cnt; to[cnt] = v; f[cnt] = flow;
    nxt[++ cnt] = lst[v]; lst[v] = cnt; to[cnt] = u; f[cnt] = 0;
}

int dfs(int u){
    if (u == t) return 1;
    tag[u] = indx;

    for (int j = lst[u]; j; j = nxt[j]){
        int v = to[j];

        if (!f[j] || tag[v] == indx) continue;

        if (dfs(v)){
            f[j] --; f[j ^ 1] ++;
            return 1;
        }

    }

    return 0;
}

int main(){

    scanf("%d%d%d", &n1, &n2, &m);
    for (int i = 1, u, v; i <= m; i ++){
        scanf("%d %d", &u, &v);
        add(u, n1 + v, 1); deg[u] ++; deg[n1 + v] ++;
    }

    int T = deg[1];
    for (int i = 2; i <= n1 + n2; i ++)
        T = min(T, deg[i]);

    s = n1 + n2 + 1, t = s + 1;

    for (int i = 1; i <= n1; i ++)
        add(s, i, deg[i] - T);
    for (int i = 1; i <= n2; i ++){

        add(n1 + i, t, deg[n1 + i] - T);

    }

    for (int cvr = T; cvr >= 0; cvr --){
        indx ++;

        while(dfs(s)) indx ++;

        for (int i = 1; i <= m; i ++)
            if (f[2 * i]) ans[cvr][++ ans[cvr][0]] = i;

        for (int i = 1; i <= n1; i ++)
            f[2 * (m + i)] ++;

        for (int i = 1; i <= n2; i ++)
            f[2 * (m + n1 + i)] ++;

    }

    for (int i = 0; i <= T; i ++){
        printf("%d ", ans[i][0]);
        for (int j = 1; j <= ans[i][0]; j ++)
            printf("%d ", ans[i][j]);

        puts("");
    }


    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值