题目:

       一开始被题目的梦幻给弄的晕乎乎。的却,作者很会yy。

       意思是:

                  一个农夫有cas个田地,然后每个田地里面有nodeNum个结点,结点之间可能有多条路径(这是一个无向图),田地里还有w个虫洞(虫洞是某种可以搞穿越的东西,即时光倒流),那么从这个虫洞开始,又有s(起点),e(终点),t(花费的时间)。正常路径的(时间——t当然是正数的。但是虫洞的t(时间)却是负数的。因为时光倒流嘛。

                  最后题目要求你算算一个人是否可以时光倒流回到过去看到过去的自己。其实这个模型就是无向图求负环。比如你从a到b所需的时间为10,但是通过虫洞从b到a所花的时间是-15,就是说通过虫洞,你可以回到15秒之前的a,哈哈,10秒前你就在a,那么你当然可以看到自己咯(题外yy:至于看到自己后,能不能跟自己聊天,讲话,还只是看到一个幻想,还真不晓得。不过根据宇宙平行理论,嗯……)

解题方法:

          解题的方法就是构建一个无向图,然后用spfa算法扫描看看是否含有负环就解决啦。至于这个负环的判断嘛,如果你的spfa是用队列来做的。那么只要标志每个节点入队的次数即可,当存在节点入队的次数超过了总节点的个数时,那么证明这个图存在负环。

 

还想不通可以参考这个:

spfa是使用队列进行渐近求最短路的方法

思想为:

  1、只保存被更新但未扩展的节点(即未进队的节点)

做法:

    1、n建立一个队列,初始时队列里只有起始点,在建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点去刷新起始点到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空

  2、n期望的时间复杂度O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。
 
看看代码更健康:
/* 
   这道题花了一个小时在调整RuntimeError, 
   就因为主函数中的初始化,把点弄成了边去初始化map[][]. 
   这是白痴级别的错误 
*/ 
 
#include<iostream> 
#include<queue> 
using namespace std; 
 
int const maxNum=1001
int const Infinity=99999999
 
int map[maxNum][maxNum],dis[maxNum];//dis用来存放点的最佳路径 
int nodeNum,time[maxNum];//time用来记录结点入队的次数 
int vst[maxNum],visited[maxNum];//标志是否入队,标志点是否扫描过 
 
bool SPFA(int start)//经典的SPFA算法 

    int i,p; 
    queue<int> que; 
    memset(vst,0,sizeof(vst)); 
    memset(time,0,sizeof(time)); 
 
    for(i=1;i<=nodeNum;i++) 
        dis[i]=Infinity; 
     
    dis[start]=0
    vst[start]=1
    que.push(start); 
    time[start]++;//起点先标志入队一次 
    while(!que.empty()) 
    { 
        p=que.front(); 
        que.pop(); 
        vst[p]=0;         
        for(i=1;i<=nodeNum;i++) 
        { 
            visited[i]=true;//这里用来标志已经被扫描过的点。注意visited跟vst数组的区别 
            if(dis[p]+map[p][i]<dis[i]) 
            { 
                dis[i]=dis[p]+map[p][i]; 
                if(!vst[i]) 
                { 
                    que.push(i); 
                    time[i]++; 
                    if(time[i]>=nodeNum)//当同一结点入队次数超过点的总数-1,即大于等于nodeNum时,存在负环,此题的关键 
                        return true
                    vst[i]=1
                } 
            } 
        } 
    } 
    return false

 
int main(void

    int cas,n,num,i,s,e,w,j; 
    scanf("%d",&cas);//田地的个数 
    while(cas--) 
    { 
        scanf("%d%d%d",&nodeNum,&n,&num);//结点数,边数,虫洞数 
        for(i=1;i<=nodeNum;i++) 
            for(j=1;j<=nodeNum;j++) 
                map[i][j]=Infinity; 
        for(i=1;i<=n;i++) 
        { 
            scanf("%d%d%d",&s,&e,&w);//边的起点,边的终点,走这条边所花的时间 
            if(map[s][e]>=w) 
            { 
                map[s][e]=w;//注意重边这种情况,取最小的那个 
            } 
            if(map[e][s]>=w)//双向边,应该每次都比较一下,(但是这道题目不比较也过) 
            { 
                map[e][s]=w; 
            } 
        } 
        for(i=1;i<=num;i++) 
        { 
            scanf("%d%d%d",&s,&e,&w); 
            if(map[s][e]>-w) 
                map[s][e]=-w;//这道题目这里似乎说明得不是很严谨,如果一条边原来是Infinity,而现在有一个负的边,那不就替代了么 
                             //题目似乎被理想化了,这种情况不考虑在其中,不知道说得对不对,望高手指教 
        } 
        memset(visited,0,sizeof(visited)); 
        for(i=1;i<=nodeNum;i++)//考虑到图可能不是完全连通图,即存在离散的子图 
        { 
            if(visited[i])    continue;//增加这一步以减少不必要的计算 
            if(SPFA(i)) 
            { 
                 cout<<"YES"<<endl; 
                break
            } 
        }                                                                                                                                                                                             if(i==nodeNum+1
            cout<<"NO"<<endl; 
    } 
    return 0

posted on 2011-08-14 23:12  cchun  阅读(210)  评论(0编辑  收藏  举报