HDU 3488--Tour(KM or 费用流)

因为每个点只能经过一次 所以考虑拆点

这题有坑,有重边。。

KM算法

把一个点拆成入点和出点 入点在X部,出点在Y步。

如果u,v之间有路径,就在X部的u点连接Y部的v点

求完美匹配。

当完美匹配的时候,每个点都有一个入度和一个出度,可知成环。

因为完美匹配求得是最大匹配

记得把每条边权值取相反数

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;
const int MAXN = 205;
const int INF = 0x3f3f3f3f;

int G[MAXN][MAXN];
int vx[MAXN], vy[MAXN];
bool visx[MAXN], visy[MAXN];
int match[MAXN];
int slack[MAXN];

int N, M;

bool dfs(int x)
{
    visx[x] = true;

    for (int y = 0; y < N; ++y) {

        if (visy[y]) continue;

        int gap = vx[x] + vy[y] - G[x][y];

        if (gap == 0) {
            visy[y] = true;
            if (match[y] == -1 || dfs( match[y] )) {
                match[y] = x;
                return true;
            }
        } else {
            slack[y] = min(slack[y], gap);
        }
    }

    return false;
}

int KM()
{
    memset(match, -1, sizeof match);
    memset(vy, 0, sizeof vy);

    for (int i = 0; i < N; ++i) {
        vx[i] = G[i][0];
        for (int j = 1; j < N; ++j) {
            vx[i] = max(vx[i], G[i][j]);
        }
    }

    for (int i = 0; i < N; ++i) {

        fill(slack, slack + N, INF);

        while (1) {
            memset(visx, false, sizeof visx);
            memset(visy, false, sizeof visy);

            if (dfs(i)) break;

            int d = INF;
            for (int j = 0; j < N; ++j)
                if (!visy[j]) d = min(d, slack[j]);

            for (int j = 0; j < N; ++j) {
                if (visx[j]) vx[j] -= d;
                if (visy[j]) vy[j] += d;
                else slack[j] -= d;
            }
        }
    }

    int res = 0;
    for (int i = 0; i < N; ++i)
        res += G[ match[i] ][i];

    return res;
}

int Scan() {
    int res = 0, flag = 0;
    char ch;
    if((ch = getchar()) == ‘-‘) flag = 1;
    else if(ch >= ‘0‘ && ch <= ‘9‘) res = ch - ‘0‘;
    while((ch = getchar()) >= ‘0‘ && ch <= ‘9‘)
        res = res * 10 + (ch - ‘0‘);
    return flag ? -res : res;
}

int main()
{
    int T = Scan();
    while (T--) {
        N = Scan(), M = Scan();
        for (int i = 0; i <= N; ++i) {
            for (int j = 0; j <= N; ++j) {
                G[i][j] = -INF;
            }
        }
        int u, v, w;
        while (M--) {
            u = Scan()-1, v = Scan()-1, w = Scan();
            G[u][v] = max(G[u][v], -w);
        }

        printf("%d\n", -KM());
    }
    return 0;
}

费用流

就是把上面的完美匹配用网络流来求。。

和完美匹配一样

如果u,v之间有路径,就u的入点连v的出点,然后所有入点连起点,所有出点连汇点,求最大流最小花费即可。

费用流比起KM慢了几倍。。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <bitset>
#include <cstdio>
#include <queue>
#include <stack>
#include <cmath>
#include <list>
#include <map>
#include <set>
#define pk(x) printf("%d\n", x)
using namespace std;
#define PI acos(-1.0)
#define EPS 1E-6
#define clr(x,c) memset(x,c,sizeof(x))
//#pragma comment(linker, "/STACK:102400000,102400000")

typedef long long ll;
#define CLR(x, v, n) memset(x, v, sizeof(x[0])*n)
const int N = 410;
const int M = 1000000;
const int INF = 0x3f3f3f3f;

struct Edge {
    int to, next, cap, flow, cost;
    void init(int _to, int _cap, int _cost, int _next) {
        to = _to; cap = _cap; cost = _cost; next = _next; flow = 0;
    }
} edge[M];

int head[N], cntE;
int pre[N], dis[N];
bool vis[N];
int src, sink, tot;

void dn(int &x, int y) { if(x>y) x=y; }

void init() {
    cntE = 0;
    memset(head, -1, sizeof head);
}

void addedge(int u, int v, int cap, int cost) {
    edge[cntE].init(v, cap, cost, head[u]); head[u] = cntE++;
    edge[cntE].init(u, 0, -cost, head[v]); head[v] = cntE++;
}

bool spfa() {
    queue<int> q;
    fill(dis, dis+tot, INF); CLR(vis, false, tot); CLR(pre, -1, tot);
    dis[src] = 0; vis[src] = true;
    q.push(src);
    while (q.size()) {
        int u = q.front(); q.pop(); vis[u] = false;
        for (int i = head[u]; ~i; i = edge[i].next) {
            int v = edge[i].to;
            if (edge[i].cap > edge[i].flow && dis[u]+edge[i].cost < dis[v]) {
                dis[v] = dis[u]+edge[i].cost;
                pre[v] = i;
                if (!vis[v]) {
                    vis[v] = true;
                    q.push(v);
                }
            }
        }
    }
    if (pre[sink] == -1) return false;
    return true;
}

int MCMF() {
    int flow = 0;
    int cost = 0;
    while (spfa()) {
        int f = INF;
        for (int i = pre[sink]; ~i; i = pre[edge[i^1].to]) {
            dn(f, edge[i].cap - edge[i].flow);
        }
        for (int i = pre[sink]; ~i; i = pre[edge[i^1].to]) {
            edge[i].flow += f;
            edge[i^1].flow -= f;
            cost += edge[i].cost * f;
        }
        flow += f;
    }
    //return flow;
    return cost;
}

int Scan() {
    int res = 0, flag = 0;
    char ch;
    if((ch = getchar()) == ‘-‘) flag = 1;
    else if(ch >= ‘0‘ && ch <= ‘9‘) res = ch - ‘0‘;
    while((ch = getchar()) >= ‘0‘ && ch <= ‘9‘)
        res = res * 10 + (ch - ‘0‘);
    return flag ? -res : res;
}

int n, m;
int G[205][205];
int main()
{
    int T = Scan();
    while (T--) {
        clr(head, -1);
        cntE = 0;
        n = Scan(), m = Scan();
        int u, v, w;
        src = 0, sink = n*2+1, tot = n*2+2;;
        for (int i = 1; i <= n; ++i) {
            addedge(src, i, 1, 0);
            addedge(i+n, sink, 1, 0);
        }
        for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) G[i][j] = INF;
        while (m--) {
            u = Scan(), v = Scan(), w = Scan();
            G[u][v] = min(G[u][v], w);
        }
        for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) {
            if (G[i][j] != INF) {
                addedge(i, j+n, 1, G[i][j]);
            }
        }
        printf("%d\n", MCMF());
    }
    return 0;
}

时间: 2024-12-28 01:17:39

HDU 3488--Tour(KM or 费用流)的相关文章

Hdu 3488 Tour (KM 有向环覆盖)

题目链接: Hdu 3488 Tour 题目描述: 有n个节点,m条有权单向路,要求用一个或者多个环覆盖所有的节点.每个节点只能出现在一个环中,每个环中至少有两个节点.问最小边权花费为多少? 解题思路: 因为每个节点就出现一个,那么每个节点出度和入度都为1咯.我们可以对每个节点u拆点为u,u',分别放在集合X,Y.然后对两个集合进行完备匹配.完备匹配成功以后,每个节点就会有只有一个出度,一个入度的. 用KM求最小匹配的话,先初始化maps为-INF,然后把各边权值存为负,求出最大值取反即可. 1

hdu 4862 Jump 上下界费用流

对于每个点拆点成为两个点a,b,连接a到b的上界为1,下界为1的边,保证用过一次且仅一次. 然后若点u可到达点v,则连接即可.建成了一个上下界网络,将下界拆出去,求最大费用最大流就好. #include <stdio.h> #include <iostream> #include <string.h> using namespace std; const int N=800; const int MAXE=200000; const int inf=1<<3

HDU 3667 Transportation(网络流之费用流)

题目地址:HDU 3667 这题的建图真是巧妙...为了保证流量正好达到k,需要让每一次增广到的流量都是1,这就需要把每一条边的流量都是1才行.但是每条边的流量并不是1,该怎么办呢.这个时候可以拆边,反正c最多只有5,拆成5条流量为1的边.但是这时候费用怎么办呢,毕竟平方的关系不能简单把每一条边加起来.这时候可以把拆的边的流量设为1,3,5,7,9.如果经过了3个流量,那就肯定会流1,3,5,费用为9,是3的平方,同理,其他的也是如此.然后按照给出的边建图跑一次费用流就可以了. 代码如下: #i

hdoj 3488 Tour 【最小费用最大流】【KM算法】

Tour Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others) Total Submission(s): 2299    Accepted Submission(s): 1151 Problem Description In the kingdom of Henryy, there are N (2 <= N <= 200) cities, with M (M <= 3000

HDU 1853--Cyclic Tour【最小费用最大流 &amp;&amp; 有向环最小权值覆盖 】

Cyclic Tour Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/65535 K (Java/Others) Total Submission(s): 1950    Accepted Submission(s): 984 Problem Description There are N cities in our country, and M one-way roads connecting them. Now L

【进阶——最小费用最大流】hdu 1533 Going Home (费用流)Pacific Northwest 2004

题意: 给一个n*m的矩阵,其中由k个人和k个房子,给每个人匹配一个不同的房子,要求所有人走过的曼哈顿距离之和最短. 输入: 多组输入数据. 每组输入数据第一行是两个整型n, m,表示矩阵的长和宽. 接下来输入矩阵. 输出: 输出最短距离. 题解: 标准的最小费用最大流算法,或者用KM算法.由于这里是要学习费用流,所以使用前者. 最小费用最大流,顾名思义,就是在一个网络中,不止存在流量,每单位流量还存在一个费用.由于一个网络的最大流可能不止一种,所以,求出当前网络在流量最大的情况下的最小花费.

HDU 2686 &amp;&amp; HDU 3376(网络流之费用流)

题目地址:HDU 2686       HDU 3376 这两道题目除了数据大小外是一样的.前者只有30*30,但是后者却成了600*600..本来以为前者代码用到后者会超时,迟迟没敢交,但是感觉能用费用流的话也只能这么做了,于是改了改数组大小就交上去了.还真没超时.. 这题又是一道关于来回最短路的.最大费用可以把费用改成相反数,最后再转成相反数就是最大费用了. 建图思路是拆点,限制每个点只能经过一次.然后将每个点与右边的和下边的连边.源点与汇点要设为2个流量. 不知道为什么用G++叫就一直WA

HDU 3395 Special Fish(最大费用流)

Special Fish Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 1814    Accepted Submission(s): 678 Problem Description There is a kind of special fish in the East Lake where is closed to campus o

POJ 2135 Farm Tour(网络流之费用流)

题目地址:POJ 2135 来回走一遍可以看成从源点到汇点走两遍.将每个点的流量设为1,就可以保证每条边不重复.然后跑一次费用流就行了.当流量到了2之后停止,输出此时的费用. #include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <ctype.h> #include <qu