poj-2679

//10332K    157MS   G++
#include <cstdio>
#include <queue>

using namespace std;

#define INF 9999999
#define UNREACH -1
#define TOWN_MAX 1110
#define EDGE_MAX 12000

int sTownNum;
int sEdgeNum;
int sRewardingRoadNum;

struct Road{
    int distance; // UNREACH means no road or not rewarding road.
    int fee;
};
typedef struct Road Road;    

struct Edge {
    int begin;
    int end;
    int distance;
    int fee;
};
typedef struct Edge Edge;

Road sRoadsInfo[TOWN_MAX][TOWN_MAX];
Edge sEdges[EDGE_MAX];
int sBeginTown;
int sEndTown;
int sMinFeeRoadForTown[TOWN_MAX];
int sFee[TOWN_MAX];
int sPredecessor[TOWN_MAX];
int sDistanceFromBegin[TOWN_MAX];
char sCanReachEnd[TOWN_MAX];
char sBFSCheckFlag[TOWN_MAX];
queue<int> sBFSQueue;


void reset() {
    sTownNum = 0;
    sEdgeNum = 0;
    while(sBFSQueue.size()) {
        sBFSQueue.pop();    
    }
    sRewardingRoadNum = 0;
    for (int i = 0; i < TOWN_MAX; i++) {
        sPredecessor[i] = -1;
        sCanReachEnd[i] = 0;
        sBFSCheckFlag[i] = 0;
        sMinFeeRoadForTown[i] = INF;
        sDistanceFromBegin[i] = -1;
        for (int j = 0; j < TOWN_MAX; j++) {
            sRoadsInfo[i][j].distance = UNREACH;
        }
    }
}

void getRewardingRoad() {
    int roadId = 0;
    // printf("getRewardingRoad %d %d\n", sTownNum, sTownNum);
    for (int i = 0; i < sTownNum; i++) {
        for (int j = 0; j < sTownNum; j++) {
            // printf("getRewardingRoad %d %d %d %d\n",i, j, sRoadsInfo[i][j].fee, sMinFeeRoadForTown[i]);
             // this road is rewarding and it is really road
            if (sRoadsInfo[i][j].fee == sMinFeeRoadForTown[i] && sRoadsInfo[i][j].distance >= 0) {
                sEdges[roadId].begin = i;
                sEdges[roadId].end = j;
                sEdges[roadId].distance = sRoadsInfo[i][j].distance;
                // if (roadId == 5309) {
                //     printf("%d %d %d\n", sEdges[roadId].begin, sEdges[roadId].end, roadId);
                // }
                sEdges[roadId++].fee = sRoadsInfo[i][j].fee;
            } else {
                sRoadsInfo[i][j].distance = UNREACH; // this road is not rewarding, not considerred
            }
        }
    }
    sRewardingRoadNum = roadId;
}

void checkIfNodesReachEnd() {
    sCanReachEnd[sEndTown] = true;
    sBFSQueue.push(sEndTown);
    sBFSCheckFlag[sEndTown] = true;
    while(sBFSQueue.size() != 0) {
        int curId = sBFSQueue.front();
        sBFSQueue.pop();
        for (int i = 0; i < sTownNum; i++) {
            if (!sBFSCheckFlag[i] && sRoadsInfo[i][curId].distance != -1) { // note, here check the reverse connectivity, check if i can reach curId
                sBFSCheckFlag[i] = true;
                sCanReachEnd[i] = true;
                sBFSQueue.push(i);
            }                
        }
    }
    // for (int i = 0; i < sTownNum; i++) {
    //     if (sCanReachEnd[i]) {
    //         printf("%d can reach %d\n", i, sEndTown);
    //     }
    // }
}

char Bellman_Ford() {
    int beginId = sBeginTown;
    int endId = sEndTown;
    // System.out.println(beginId + " " + endId);
    for (int i = 0; i < sTownNum; i++) {
        sFee[i] = INF;
    }
    sFee[beginId] = 0;
    sPredecessor[beginId] = -1;
    sDistanceFromBegin[beginId] = 0;
    for (int i = 1; i < sTownNum; i++) {
        for (int j = 0; j < sRewardingRoadNum; j++) {
            int begin = sEdges[j].begin;
            int end = sEdges[j].end;
            // if (end == INF) {
            //     printf("%d %d %d %d\n", begin, end, j, sRewardingRoadNum);
            // }
            if (sCanReachEnd[begin] && sCanReachEnd[end]) {
                int fee = sEdges[j].fee;
                int distance = sEdges[j].distance;
                int orginal = sFee[end];
                int current = 0;
                if (sFee[begin] == INF) {  // INF + any still INF!
                    current = INF;
                } else {
                    current = sFee[begin] + fee;
                }
                if (orginal > current) { // need relax
                    sFee[end] = current;
                    sPredecessor[end] = begin;
                    sDistanceFromBegin[end] = sDistanceFromBegin[begin] + distance;
                } else if (orginal == current) {
                    if (sPredecessor[end] != - 1) { // if already has predecessor
                        int newDistanceFromBegin = sDistanceFromBegin[begin] + distance;
                        if (sDistanceFromBegin[end] > newDistanceFromBegin) { // need update
                            sFee[end] = current;
                            sPredecessor[end] = begin;
                            sDistanceFromBegin[end] = sDistanceFromBegin[begin] + distance;
                        }
                    }
                }
            }
            // sFee[end] = orginal < current ? orginal: current;
        }
    }
    // System.out.println(sFee[endId] + " " + sDistanceFromBegin[endId]);
    //check if unbound
    // if (sFee[endId] != INF) {
    //     int curId = endId;
    //     while(true) {
    //         if (curId == sBeginTown || curId == -1) {
    //             break;
    //         } else {
    //             // System.out.println(curId + " -> " + sPredecessor[curId]);
    //             // if (sFee[curId] > sFee[sPredecessor[curId]] + sRoadsInfo[sPredecessor[curId]][curId].fee) {
    //             //     return false;
    //             // }
    //             curId = sPredecessor[curId];
    //         }
    //     }
    // }
    for (int j = 0; j < sRewardingRoadNum; j++) {
        int begin = sEdges[j].begin;
        int end = sEdges[j].end;
        int fee = sEdges[j].fee;
        if (sCanReachEnd[begin] && sCanReachEnd[end]) {
            if (sFee[end] > sFee[begin] + fee) {
                // System.out.println(end + " " + begin + " " + sFee[end] + " " + sFee[begin] + " " + fee);
                return 0;
            }                
        }
    }
    return 1;
}

void solve() {
    // printf("getRewardingRoad\n");
    getRewardingRoad();
    // printf("%d\n", sRewardingRoadNum);
    // for (int i = 0; i < sRewardingRoadNum; i++) {
    //     printf("%d -> %d  d: %d fee: %d", sEdges[i].begin, sEdges[i].end,
    //                     sEdges[i].distance, sEdges[i].fee);
    // }
    // printf("checkIfNodesReachEnd\n");
    checkIfNodesReachEnd();
    if (!sCanReachEnd[sBeginTown]) { // if begin can not reach end
        printf("VOID\n");
        // reset();
        return;
    }
    // printf("Bellman_Ford\n");
    char res = Bellman_Ford();
    // printf("after Bellman_Ford\n");
    if (!res) {
        printf("UNBOUND\n");
    } else {
        // System.out.println(sFee[sEndTown] == INF ? "VOID" : sFee[sEndTown]);
        if (sFee[sEndTown] == INF) {
            printf("VOID\n");
        } else {
            printf("%d %d\n", sFee[sEndTown], sDistanceFromBegin[sEndTown]);
        }
    }
    // reset();
}

    // public Main() {
    //     sBFSCheckFlag = new boolean[TOWN_MAX];
    //     sCanReachEnd = new boolean[TOWN_MAX];
    //     sBFSQueue = new LinkedList<Integer>();
    //     sDistanceFromBegin = new long[TOWN_MAX];
    //     sPredecessor = new int[TOWN_MAX];
    //     sFee = new int[TOWN_MAX];
    //     sMinFeeRoadForTown = new int[TOWN_MAX];
    //     sRoadsInfo = new Road[TOWN_MAX][TOWN_MAX];
    //     for (int i = 0; i < TOWN_MAX; i++) {
    //         for (int j = 0; j < TOWN_MAX; j++) {
    //             sRoadsInfo[i][j] = new Road();
    //         }
    //     }
    //     sEdges = new Edge[EDGE_MAX];
    //     for (int i = 0; i < EDGE_MAX; i++) {
    //         sEdges[i] = new Edge();
    //     }
    //     reset();
    // }

int main() {
    // int sTownNum;
    // int sEdgeNum;
    // int sBeginTown;
    // int sEndTown;
    reset();
    while(scanf("%d %d %d %d", &sTownNum, &sEdgeNum, &sBeginTown, &sEndTown) != EOF) {
        for (int i = 0; i < sEdgeNum; i++) {
            int from;
            int end;
            int toFee;
            int distance;
            int fromFee;
            char st[50];
            scanf("%s", st);
            sscanf(st, "(%d,%d,%d[%d]%d)", &from, &end, &toFee, &distance, &fromFee);
            // scanf("(%d,%d,%d[%d]%d)", &from, &end, &toFee, &distance, &fromFee);
            // printf("%d %d %d %d %d\n", from, end, toFee, distance, fromFee);
            char needUpdate = 0;
            if (sRoadsInfo[from][end].distance == -1) {
                needUpdate = 1;
            } else { // multi road from -> end, choose the min fee, if fee equals, choose min distance
                if (toFee < sRoadsInfo[from][end].fee) {
                    needUpdate = 1;
                } else if (toFee == sRoadsInfo[from][end].fee) {
                    if (distance < sRoadsInfo[from][end].distance) {
                        needUpdate = 1;
                    }
                }
            }

            if (needUpdate) {
                sRoadsInfo[from][end].distance = distance;
                sRoadsInfo[from][end].fee = toFee;
                sMinFeeRoadForTown[from] = sMinFeeRoadForTown[from] < toFee ?
                                sMinFeeRoadForTown[from] : toFee;
            }


            needUpdate = 0;

            if (sRoadsInfo[end][from].distance == -1) {
                needUpdate = 1;
            } else { // multi road end -> from, choose the min fee, if fee equals, choose min distance
                if (fromFee < sRoadsInfo[end][from].fee) {
                    needUpdate = 1;
                } else if (fromFee == sRoadsInfo[end][from].fee) {
                    if (distance < sRoadsInfo[end][from].distance) {
                        needUpdate = 1;
                    }
                }
            }

            if (needUpdate) {
                sRoadsInfo[end][from].distance = distance;
                sRoadsInfo[end][from].fee = fromFee;
                sMinFeeRoadForTown[end] = sMinFeeRoadForTown[end] < fromFee ?
                                sMinFeeRoadForTown[end] : fromFee;
            }
        }
        solve();
        reset();
    }
}

10332K    157MS   G++

测试数据 http://acm.ro/2005/problems.htm    proble F

曲折的一题,用的虽然是很简单的BF算法,但是转化以及求解过程比较挑战。

首先题目本身就有点难懂,读了几遍才清楚要求的路径,首先,必须是rewarding的路,然后,路径的权值是最小的,最后,如果还有几条路径,那么找一个路径长度最短的.

rewarding这个含义就比较费解,后来搞清楚了就是每次从一个点出发时,必须从该点所有出路中权值最小的路出发,这样的路径才是rewarding。

这个限制其实算是简化了题吧,不过要知道一点,就是虽然要求每次从权值最小路出发,不代表每次只有一条路可走,因为可能会有好几条出路,他们的权值相等,且是最小的权值,因此还是会有路径选择问题的。

不过rewarding这个条件可以过滤掉一堆不符合条件的边了,

因此在输入完了以后,应该首先做的就是去掉那些不是最小权值出路的边,这个预处理可以大大简化运算量。

预处理以后,就得到了一个真正都是rewarding路径的图G,满足了最优路径的第一个要求,

然后就是求最小权值的路径了,因此这次的权值有负数,因此要用BF算法来求,这时候,就到本题的一个重点了:

在这张图G中,是可能会存在负权环路的,BF可以检测出来,但是,BF检测是否有负向环路是针对从起始点到所有其他点是否可能在某几条路径有负权环路,

而本题中,我们要检查的是在从起始点到终点的路径中,是否可能会有负权环路,BF检测出来有负向环路,但是从起点到终点的路径不一定就有负权环路。

比如 四个点 1 ,2 ,3 ,4

1->2

1->3

3->4

4->3,并且 3到4是一个负权环路。

如果要从1 到 2,是不会受到这个负向环路的影响的。因为这个负向环路每一个点i,   都是不可达2的(这样就不可能在从1到2的路径中出现负权环路, 因此一旦进入了负权环路,就不能再到2了,这个路径就不是解)。

所以,要检测这个情况,就要利用上面的推论,只检测,能到达B(2)的这些点,是否会有负向环路就可以了。

也就是说,还要对图进行一次处理,使它只剩下能到达2的点。

找这些点也简单,就是利用G的反向图,从终点E做一次BFS,所有遍历到的点都是可以到达E的。(甚至不用反向图这个思维概念,只把BFS选取点N的条件从                                     原来的从当前点可达N  变为从N可达当前点就可以)

而这个反向图也不必再重新求,只要把原来的邻接矩阵 M的x和y换一下就可以(邻接表似乎就不是很方便了)。

就对于点 i,M[i][2] 代表的就是 i到E的距离, 在反向图里就是 M[2][i]。

这里,因为之前已经邻接矩阵构造好了,在得到了能到达E的点集之后,不想再次修改邻接矩阵了,

就偷懒搞了一个flag数组R,R[i]来标示从点i能否到达E,这里就可以先判断一次从起始点B能否到达E,如果不能,直接VOID

在后面的BF中,每次在判断前, 取得边的begin 和 end 点以后,现看看这个两个点是否都是 可以到达E的,如果不全部可达E,那么这条边就不需要考虑,

不必参与到算法中,这样就也能保证了BF只判断了那些能够到达E的点集的最短路以及是否有负权回路。

在BF中,还要考虑最优路径的第3个条件,在满足最小权值的情况下,的最短路径:

要实现这个,在进行 d[v] 和 d[u] + w{u,v}的大小比较时,正常的BF,只有在d[v] 大于d[u] + w{u,v} 才会对路径进行松弛,替换点v的前驱节点(就是最优路径的上一个点)

而在这里,要实现最短路径,还要加一个判断,

首先,在d[v] 和 d[u] + w{u,v} 大小明确的情况下,不做任何改变,否则最后得到的就不是最小权值的路径,

但是如果d[v] == d[u] + w{u,v} 时,BF是不做松弛的,这种case等于有了两个同样权值大小的选择,那么在这时候,就选择路径最小的即可。

这样,对最小权值路径的形成没有做任何的干预,最后必然是最小权值,

而在可以选择的情况下,又选择了最短路径的,这样最后得到就是最小权值条件下的最短路径。 

 这也是一个trick,值得记住。

最后,如果BF检测有负环,就UNBOUND,

如果发现终点E到B的权值是初始的无穷大(在实际coding要保证无穷大和任何数运算的结果也是无穷大),那么VOID。

一开是用java搞,但是因此该题奇葩的输入方式,导致在输入那里就RE了,

不得已又移到了C++,结果边数组的大小又开小了(注意,input的一条边很其实可能代表了两条边的,要开成题目给的上限的两倍多)。

虽然BF算法是最笨的,最慢的,但是却有思想的,要好好看看证明.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值