P1807 最长路 题解

题目传送门

一、理解与感悟

有向图有负权边

比如从1到n有两步,一步是权值是100,另一步权值是-100,如果初值设置为0,就搞不清楚是加起来之后是0,还是根本没办法到达。

如果初值设置为-1,那么也是一个样,不知道是天生-1,还是运算完是-1。

设置初值为INF是解决负权边的有效办法!!!

二、vector方式建图解法

#include <bits/stdc++.h>

using namespace std;
//拓扑排序+vector实现
const int N = 50000 + 10;
const int INF = 0x3f3f3f3f;

//结构体
struct Edge {
    int to;    //下一个结点
    int value; //边长
    int next;  //索引值
};
vector<Edge> p[N];

int ind[N]; //入度
int f[N];   //动态规划的结果
queue<int> q;   //拓扑排序用的队列
int n; //n个顶点
int m; //m条边

int main() {
    //读入
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        int u, v, w;//代表存在一条从 u 到 v 边权为 w 的边。
        cin >> u >> v >> w;
        p[u].push_back({v, w});
        ind[v]++;//统计入度个数
    }
    //入度为0的结点入队列,进行拓扑排序
    for (int i = 1; i <= n; i++) if (!ind[i]) q.push(i);

    //初始化,将到达节点1的距离设为最大值,这个用的太妙了~~
    //防止出现负权边,也防止出现了0不知道是权边加在一起出现的,还是天生就是0
    //调高起点值看来是解决负权边的重要方法,学习学习。
    f[1] = INF;

    //拓扑排序
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        //遍历每条出边
        for (int i = 0; i < p[u].size(); i++) {
            int y = p[u][i].to;
            --ind[y]; //在删除掉当前结点带来的入度
            //精髓!~
            //运用拓扑排序的思想,对每个节点进行访问,并在此处用上动态规划的思路更新到此节点的最大距离
            f[y] = max(f[y], f[u] + p[u][i].value); //利用走台阶思路,从上一级走过来
            if (!ind[y]) q.push(y);//在删除掉当前结点带来的入度后,是不是入度为0了,如果是将点y入队列
        }
    }
    //如果可以到达,则输出最长路径
    if (f[n] > 0)printf("%d", f[n] - INF);
    else printf("-1");
    return 0;
}

三、链式前向星建图解法

#include <bits/stdc++.h>

using namespace std;
//拓扑排序+链接式前向星实现
const int N = 50000 + 10;
const int INF = 0x3f3f3f3f;

//链式前向星
struct Edge {
    int to;    //下一个结点
    int value; //边长
    int next;  //索引值
} edge[N];     //边数,也不可能多于结点数,因为这里是指每个结点引出的边数集合

int idx;    //索引下标
int head[N];//拉链的链表
int ind[N]; //入度
int f[N];   //动态规划的结果

queue<int> q;   //拓扑排序用的队列

//加入一条边,x起点,y终点,value边权
void add_edge(int x, int y, int value) {
    edge[++idx].to = y;
    edge[idx].value = value;
    edge[idx].next = head[x];
    head[x] = idx;
}

int n; //n个顶点
int m; //m条边

int main() {
    //读入
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        int u, v, w;//代表存在一条从 u 到 v 边权为 w 的边。
        cin >> u >> v >> w;
        add_edge(u, v, w);
        ind[v]++;//统计入度个数
    }
    //入度为0的结点入队列,进行拓扑排序
    for (int i = 1; i <= n; i++) if (!ind[i]) q.push(i);

    //初始化,将到达节点1的距离设为最大值,这个用的太妙了~~
    //防止出现负权边,也防止出现了0不知道是权边加在一起出现的,还是天生就是0
    //调高起点值看来是解决负权边的重要方法,学习学习。
    f[1] = INF;

    //拓扑排序
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        //遍历每条出边
        for (int i = head[u]; i; i = edge[i].next) {
            int y = edge[i].to;
            --ind[y]; //在删除掉当前结点带来的入度
            //精髓!~
            //运用拓扑排序的思想,对每个节点进行访问,并在此处用上动态规划的思路更新到此节点的最大距离
            f[y] = max(f[y], f[u] + edge[i].value); //利用走台阶思路,从上一级走过来
            if (!ind[y]) q.push(y);//在删除掉当前结点带来的入度后,是不是入度为0了,如果是将点y入队列
        }
    }
    //如果可以到达,则输出最长路径
    if (f[n] > 0)printf("%d", f[n] - INF);
    else printf("-1");
    return 0;
}
原文地址:https://www.cnblogs.com/littlehb/p/15127505.html