SPOJ 412 K-path cover 匹配

http://hi.baidu.com/roba/blog/item/c842fdfac10d24dcb48f31d7.html

roba的解说


题目链接

题目大意就是说,给N个点,每个点都有一个权值,某些点之间存在有向边,有向边的权值为它的两端点权值之和。现在要求从中选出K条边,使得没有任何两条边共头或共尾,问K条边的权值和最小为多少。数据范围大的吓人,N<=10^4,边数M<=10^6,但是K<=100。

容易想到一个网络流(or 匹配?)的做法,就是把每个点p拆成两个(p,p'),然后对于原图中的边(u,v),在新图中加边(u,v'),权值不变。这样问题就转化为在新的二分图中求出一个包含K条边的最小带权匹配。这个问题可以转化成最小费用流解决:添加源和汇,源到所有左子集的点添加容量为1,费用为0的边;所有右子集到汇添加容量为1,费用为0的边;中间的边容量为1,费用为原权值。然后求一个流值为K的最小费用流。

然而常用的最小费用流算法复杂度太高,比如用Bellman-Ford找最小费用路,那么复杂度就高达O(MNK),显然不可接受。到网上四处去问, Jin Bin大牛给了一个 ahyangyi的解答,是贪心一个初始流,先选出尽可能小的K条边,另外 Adrian Kuegel给了一个 非常漂亮的解法,简述如下:

朴素的费用流算法没有利用到题目中“边的权值是它的两端点权值之和”这一条件。考虑一下类似匈牙利算法找交错链的过程,实际上,在这个条件下,可以发现每次增广后增加的费用就是新加入匹配的两个点的权值。比如当前的匹配是(b,a),现在发现一条交错链(c,a),(a,b),(b,d),则增广后新的匹配变为(c,a),(b,d),增加的权值就是c点和d点的权值。由于这个特点,我们可以在每次增广的时候,枚举左子集中当前未匹配的点作为起点,用BFS或DFS查找交错链,记录下每条交错链的起点和终点的权值和,在所有这些权值和中取最小的一对进行增广。注意如果把点按权值排序后,每个点就只须访问一次,故每次增广的时间复杂度就是遍历图的O(M+N),共需增广K次,故总的复杂度为O((M+N)*K)。


OK,然后是代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#define eps 1e-5
#define MAXN 11111
#define MAXM 1111111
#define INF 1000000007
using namespace std;
int k, n, m;
struct P
{
    int w, id;
    bool operator <(const P &a)const
    {
        return w < a.w;
    }
}p[MAXN];
struct EDGE
{
    int v, next;
}edge[MAXM];
int head[MAXN], e;
int id[MAXN], used[MAXN], lx[MAXN], ly[MAXN], fa[MAXN], ha[MAXN];
int ans;
void init()
{
    e = 0;
    ans = 0;
    memset(head, -1, sizeof(head));
    memset(used, 0, sizeof(used));
    memset(lx, 0, sizeof(lx));
    memset(ly, 0, sizeof(ly));
}
void add(int x, int y)
{
    edge[e].v = y;
    edge[e].next = head[x];
    head[x] = e++;
}
void dfs(int u, int f)
{
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].v;
        if(!fa[v])
        {
            fa[v] = u;
            if(!ly[v]) ha[v] = f;
            else dfs(ly[v], f);
        }
    }
}
bool find()
{
    memset(fa, 0, sizeof(fa));
    for(int i = 1; i <= n; i++)
        if(!used[i]) dfs(i, i);
    int mi = INF, pos = -1;
    for(int i = 1; i <= n; i++)
        if(!ly[i] && fa[i])
            if(mi > p[i].w + p[ha[i]].w)
            {
                mi = p[i].w + p[ha[i]].w;
                pos = i;
            }
    if(pos == -1) return 0;
    ans += mi;
    int u;
    for(u = pos; fa[u] != ha[pos];)
    {
        ly[u] = fa[u];
        int tmp = lx[fa[u]];
        lx[fa[u]] = u;
        u = tmp;
    }
    ly[u] = ha[pos];
    lx[ha[pos]] = u;
    used[ha[pos]] = 1;
    return 1;
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d%d", &k, &n, &m);
        for(int i = 1; i <= n; i++) scanf("%d", &p[i].w), p[i].id = i;
        sort(p + 1, p + n + 1);
        for(int i = 1; i <= n; i++) id[p[i].id] = i;
        int u, v;
        init();
        for(int i = 1; i <= m; i++)
        {
            scanf("%d%d", &u, &v);
            add(id[u], id[v]);
        }
        int flag = 1;
        for(int i = 1; i <= k; i++)
            if(!find()){flag = 0; break;}
        if(!flag) printf("NONE\n");
        else printf("%d\n", ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值