HIT 2739 The Chinese Postman Problem

HIT_2739

    这个题目实际上相当于问至少给原图补多长的边才能够成一个欧拉回路,只不过这些补的边只能是由原图的若干条边拼接成的。

    对于入度大于出度的点应该和出度大于入度的点相连,如果确定点a和点b要连接的话,那么补的边应该是a到b的最短路,只不过我们现在不知道究竟应该将哪些点连接起来。

    其实可以构造费用流来解决到底哪些点相连的问题,首先将点按入度大于出度的(不妨记作a类点)和出度大于入度的(不妨记作b类点)分成两组,至于入度和出度相等的点是可以忽略的。然后将源点和a类点连容量为入度-出度,费用为0的点,同样的将b类点和汇点连容量为出度-入度,费用为0的点,这样就通过容量保证了每个点最终会变成入度和出度相等的点。接下来考虑费用,将a类点和b类点连容量为INF,费用为a->b最短路的边,这样就保证了最终补全的边的和是最小的。最后做最小费用最大流并判断是否满流即可。

    当然,构成欧拉回路还有一个必要的条件就是原图必须连通,因此一开始可以并查集先判断一下原图是否连通。

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<stdlib.h>
#define MAXD 210
#define MAXM 20410
#define INF 0x3f3f3f3f
const int Q = 205;
int N, M, first[MAXD], e, next[MAXM], v[MAXM], flow[MAXM], cost[MAXM];
int S, T, q[MAXD], inq[MAXD], dis[MAXD], pre[MAXD], p[MAXD];
int SUM, TOT, f[MAXD][MAXD], indgr[MAXD], outdgr[MAXD];
int find(int x)
{
    return p[x] == x ? x : (p[x] = find(p[x]));    
}
void init()
{
    int i, x, y, z;
    scanf("%d%d", &N, &M);
    memset(f, 0x3f, sizeof(f));
    memset(indgr, 0, sizeof(indgr));
    memset(outdgr, 0, sizeof(outdgr));
    SUM = 0;
    for(i = 0; i < N; i ++) p[i] = i;
    for(i = 0; i < M; i ++)
    {
        scanf("%d%d%d", &x, &y, &z);
        ++ indgr[y], ++ outdgr[x], SUM += z, f[x][y] = std::min(f[x][y], z);
        p[find(x)] = find(y);
    }
}
int check()
{
    for(int i = 1; i < N; i ++) if(find(i) != find(0)) return 0;
    return 1;    
}
void floyd()
{
    int i, j, k;
    for(i = 0; i < N; i ++) f[i][i] = 0;
    for(k = 0; k < N; k ++)
        for(i = 0; i < N; i ++)
            for(j = 0; j < N; j ++)
                f[i][j] = std::min(f[i][j], f[i][k] + f[k][j]);
}
void add(int x, int y, int f, int c)
{
    v[e] = y, flow[e] = f, cost[e] = c;
    next[e] = first[x], first[x] = e ++;    
}
void build()
{
    int i, j;
    S = N, T = N + 1, TOT = 0;
    memset(first, -1, sizeof(first[0]) * (T + 1)), e = 0;
    for(i = 0; i < N; i ++)
    {
        if(indgr[i] > outdgr[i])
        {
            TOT += indgr[i] - outdgr[i];
            add(S, i, indgr[i] - outdgr[i], 0), add(i, S, 0, 0);
            for(j = 0; j < N; j ++)
                if(outdgr[j] > indgr[j] && f[i][j] != INF)
                    add(i, j, INF, f[i][j]), add(j, i, 0, -f[i][j]);    
        }
        else if(outdgr[i] > indgr[i])
            add(i, T, outdgr[i] - indgr[i], 0), add(T, i, 0, 0);
    }
}
int bfs()
{
    int i, j, x, front = 0, rear = 0;
    memset(dis, 0x3f, sizeof(dis[0]) * (T + 1));
    dis[S] = 0, pre[S] = -1, q[rear ++] = S;
    memset(inq, 0, sizeof(inq[0]) * (T + 1));
    while(front != rear)
    {
        x = q[front ++], inq[x] = 0;
        front > Q ? front = 0 : 0;
        for(i = first[x]; i != -1; i = next[i])
            if(flow[i] && dis[x] + cost[i] < dis[v[i]])
            {
                dis[v[i]] = dis[x] + cost[i], pre[v[i]] = i;
                if(!inq[v[i]])
                {
                    q[rear ++] = v[i], inq[v[i]] = 1;
                    rear > Q ? rear = 0 : 0;    
                }    
            }
    }
    return dis[T] != INF;
}
void solve()
{
    int i, c = 0, a, f = 0;
    if(!check())
    {
        printf("-1\n");
        return ;
    }
    floyd(), build();
    while(bfs())
    {
        for(i = pre[T], a = INF; i != -1; i = pre[v[i ^ 1]]) a = std::min(a, flow[i]);
        for(i = pre[T]; i != -1; i = pre[v[i ^ 1]])
            flow[i] -= a, flow[i ^ 1] += a;
        f += a, c += a * dis[T];
    }
    printf("%d\n", f == TOT ? c + SUM : -1);
}
int main()
{
    int t;
    scanf("%d", &t);
    while(t --)
    {
        init();
        solve();    
    }
    return 0;    
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值