首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

SPOJ 412 K-path cover 婚配

2012-08-21 
SPOJ 412 K-path cover匹配http://hi.baidu.com/roba/blog/item/c842fdfac10d24dcb48f31d7.htmlroba的解说

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 1000000007using 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;}


热点排行