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

你可能感兴趣的:(chinese)