拓扑排序法解决有向无环图(DAG)上的最短路问题

题目内容:

处女座想出去比赛,但是又不知道学校能不能给到足够的经费。然而处女座是大众粉丝,有着很好的人缘,于是他找了一个在学校管经费的地方勤工俭学偷来了一份报销标准。

由于处女座是万人迷,所以他在中间途径的每一条线路上都会发生一些故事,也许是粉丝给他发了一个200元的微信红包,也许是和他的迷妹一起吃饭花了500元。

而经费负责人也实地考察了每一条路线,在每一条路上,也许是天降红包雨,也许是地生劫匪。每一条路上都有属于自己的奇遇。

而经费负责人也只能根据他的故事决定这一路批下来多少经费。他会找出从宁波到比赛地的最小花费,并以此作为标准给处女座打比赛。而处女座也会选择对他来说最小花费的路线,来节省使用。
处女座想知道,最终的经费是否够用,如果够还会剩下来多少钱。如果不够,他自己要自费掏出多少钱。(当然处女座和经费管理人都具有旅途中无限信贷额度,所有收入支出会在旅行结束后一起结算。)

输入描述:
输入文件第一行包含一个整数T,表示处女座要参加的比赛场数。

对于每一场比赛,第一行包含两个整数N,M,分别表示旅行中的站点数(其中宁波的编号为1,比赛地的编号为N)和线路数。

接下来M行,每一行包含5个整数u,v,c,cnz,jffzr,分别表示从u到v有一条单向的线路,这条线路的票价为c。处女座搭乘这条线路的时候,会得到cnz元(如果为负即为失去-cnz元);经费负责人搭乘这条线路的时候,会得到jffzr元(如果为负即为失去-jffzr元)。

行程保证不会形成环,并保证一定能从宁波到达比赛地。
输出描述:
对于每一场比赛,如果经费负责人给出的经费绰绰有余,则先在一行输出"cnznb!!!",并在下一行输出他可以余下的经费;如果处女座的经费不够用,则先在一行输出"rip!!!",并在下一行输出他需要自费的金额;如果经费负责人给出的经费正好够处女座用,则输出一行"oof!!!"。(所有输出不含引号)
示例1
输入
复制
1
3 3
1 2 300 600 -600
2 3 100 -300 1
1 3 200 0 0
输出
复制
cnznb!!!
100
说明
处女座先走第一条路再走第二条路到达,总花费100元,经费负责人走第三条路,花费200元,处女座经费剩余100元
传送门

因为存在权值为负的情况, 无法用常规的三种解决最短路问题算法来解题, 上篇博客, 特殊 DAG 的性质使得 SPFA 算法无法在规定的时间限内求解出答案。 考虑到 DAG 的特殊性,按照原图节点的拓扑顺序依次递推距离即可求解。简单来说就是先得到拓扑排序的序列, 然后遍历该序列更新最短路距离。又是有向的又是拓扑排序的可以遍历序列更新出最短路径距离而无关正负,时间复杂度:?(?+?).

#include<iostream>
#include<queue>
#include<memory.h>
#include<queue>
#include<vector>
#include<cstdio>
using namespace std;
const int maxn = 1e5 + 10;
typedef long long ull;

struct node
{
    int to;
    ull a, b;
    node(int q, ull w, ull r) : to(q), a(w), b(r) {}
};
vector<node> G[maxn];
int in[maxn], n, m;
long long int  disa[maxn], disb[maxn];    			//分别保存两个人的最短路径距离
int INF = 1e17;
 
void solve()
{

    queue<int>q;
    for(int i=1;i<=n;i++)  //n  节点的总数
        if(in[i]==0) q.push(i);  //将入度为0的点入队列
    vector<int>ans;   //ans 为拓扑序列
    while(!q.empty())
    {
        int p=q.front(); q.pop(); // 选一个入度为0的点,出队列
        ans.push_back(p);
        for(int i=0;i < G[p].size();i++)
        {
            node t = G[p][i];
            if(--in[G[p][i].to]==0)
                q.push(G[p][i].to);
 
            if(disa[G[p][i].to] > disa[p] + G[p][i].a) 			//更新最短路径距离
                disa[G[p][i].to] = disa[p] + G[p][i].a;
            if(disb[G[p][i].to] > disb[p] + G[p][i].b)
                disb[G[p][i].to] = disb[p] + G[p][i].b;
 
        }
    }
 
}
 
 
int main()
{
    int t;
    scanf("%d", &t);
 
    while(t--)
    {
        int u, v;
		ull w, x, y;
        scanf("%d %d", &n, &m);
 
        for(int i = 0; i <= n; i++) 				/初始化操作
		{
			G[i].clear();
			disa[i] = INF;
			disb[i] = INF;
		}
		    disa[1] = 0;
   			 disb[1] = 0;
 
        memset(in, 0, sizeof(in));

        for(int i = 0; i < m; i++)
        {
            scanf("%d %d %lld %lld %lld", &u, &v, &w, &x, &y);
            node e = node(v, w - x, w - y);
            G[u].push_back(e);
            in[v]++;
        }
        solve();
		if(disb[n] < 0) disb[n] = 0;					//赚了需置零
		if(disa[n] < 0) disa[n] = 0;
        long long int re = disb[n] - disa[n];
        if(re > 0)
        {
            printf("cnznb!!!\n");
            printf("%lld\n", re);
        }
        else if(!re)
        printf("oof!!!\n");
        else
        {
            printf("rip!!!\n");
            printf("%lld\n", re);
        }
    }
}

这个算法搞清楚了很容易, 也就是在拓扑排序的基础上加了一些操作就可以解决此类问题,此外, 拓扑排序还可以用来判断图中是否有环的问题, 若图中有环则无法的到拓扑序列, 这里的无拓扑序列是指所得到的序列中不包含所有的顶点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值