图论 —— DAG 图的最长路

【概述】

DAG 图的最长路问题是一个比较少见的问题,具体问题是:给出一个 DAG 图,寻找图中的最长路

在 AOE 网中,在找出关键路径后,对其进行 DFS 即可得到图的最长路,由于这种方法的实现过于繁琐,这里介绍几种较为简单的实现。

【最短路算法】

对于最短路算法,Floyd,Dijkstra、Bellman-Ford、SPFA 等,将其松弛操作进行修改,即可将最短路算法变为最长路算法。

以 Floyd 为例:

int G[N][N];
void Floyd(){
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(i!=k&&j!=i&&j!=k)
                    if(g[i][j]<g[i][k]+g[k][j])
                        g[i][j]=g[i][k]+g[k][j];
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);

    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            scanf("%d",&g[i][j]);
     
    Floyd();

    int res=-INF;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            res=max(g[i][j],res);
    printf("%d\n",res);

    return 0;
}

【拓扑排序】

在拓扑排序的过程中,不断记录路径,最后对路径进行排序,输出最大的那个即为 DAG 的最长路

struct Node{
    int to,dis;
    Node(){}
    Node(int to,int dis):to(to),dis(dis){}
};
vector<Node> G[N];
int in[N];
int dis[N];
int n,m;
void topSort() {
    stack<int > S;
    for(int i=1; i<=n; i++)
        if(!in[i])
            S.push(i);


    while(!S.empty()) {
        int x=S.top();
        S.pop();
        for(int j=0; j<G[x].size(); j++) {
            int y=G[x][j].to;
            dis[y]=max(dis[y],dis[x]+G[x][j].dis);
            in[y]--;
            if(!in[y])
                S.push(y);

        }
    }
}

int main() {
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        memset(in,0,sizeof(in));
        memset(dis,0,sizeof(dis));
        for(int i=0; i<=n; i++)
            G[i].clear();

        for(int i=1; i<=m; i++) {
            int x,y,dis;
            scanf("%d%d%d",&x,&y,&dis);

            Node temp;
            temp.to=y;
            temp.dis=dis;
            in[y]++;
            G[x].push_back(temp);
        }
        topSort();

        int res=-INF;
        for(int i=1;i<=n;i++)
            res=max(res,dis[i]);
        printf("%d\n",res);
    }

    return 0;
}

【动态规划】

1.不固定终点起点

当给定一个 DAG 图时,要求整个图中所有路径中权值和最大的那条,即不固定终点和起点问题。

设 dp[i] 为从 i 点出发能获得的最长路径长度,G[i][j] 为从 i 点到 j 点的距离,这样所有的 dp[i] 的最大值就是整个 DAG 的最长路径长度,如果从 i 点出发,能直接到达顶点 j1、j2、...、jk,而 dp[j1]、dp[j2]、...、dp[k] 均已知,那么有:dp[i]=max{ dp[j]+G[i][j] }

根据上面的思路,由于最后的顶点没有出边,因此需要按照逆拓扑排序来求解 dp 数组,但可以利用递归来进行求解:由于从出度为 0 的顶点出发的最长路径长度为 0,因此边界就是这些点,在具体实现中不妨对整个 dp 数组初始化为 0,这样 DP 函数当前访问顶点i的出度为0时就会直接返回 dp[i]=0,而出度不为 0 的时候就会递归求解,递归过程中遇到已经计算过的顶点则直接返回对于的 dp 值,于是从逻辑上实现了逆拓扑排序的效果。

其基于邻接矩阵实现的代码如下:

int dp[N];//使用前整个数组设为0
int G[N][N];
int DP(int i) {
    if(dp[i]>0)
        return dp[i];
    for(int j=0; j<n; j++)//遍历i的所有可达出边
        if(G[i][j]!=INF)
            dp[i]=max(dp[i],DP(j)+G[i][j]);
    return dp[i];
}

当需要输出这条最长路时,可以利用一个 next 数组来记录 i 顶点的后继结点,再求完最长路后,顺序打印路径即可

int DP(int i) {
    if(dp[i]>0)
        return dp[i];
    for(int j=0; j<n; j++) { //遍历i的所有可达出边
        if(G[i][j]!=INF) {
            int temp=DP(j)+G[i][j];//单独计算dp
            if(dp[i]<temp) { //可以获得更长的路径
                dp[i]=temp;
                next[i]=j; //保存i的后继顶点j
            }
        }
    }
    return dp[i];
}
void printPath(int i) {//调用前需先获得最大的dp[i],然后将i作为路径的起点传入
    printf("%d",i);
    while(next[i]!=-1) { //next数组初始化为-1
        i=next[i];
        printf("->%d",i);
    }
    printf("\n");
}

2.固定终点起点

给定一个 DAG 图,给出一个起点和终点,要求从起点到终点的路径中权值和最大的那条,即固定终点和起点问题。

设规定的终点为 T,那么设 dp[i] 为从 i 号点出发到达终点 T 所能获得的最大长度,G[i][j] 为从 i 点到 j 点的距离,如果从 i 点出发,能直接到达顶点 j1、j2、...、jk,而 dp[j1]、dp[j2]、...、dp[k] 均已知,那么有:dp[i]=max{ dp[j]+G[i][j] }

可以发现,这个 dp 式子与上面不固定终点起点的问题相同,但问题的区别在于边界

第一个问题中,没有固定的终点,因此边界为所有出度为 0 的顶点,其 dp 值为 0

第二个问题中,固定了终点,因此边界应当为 dp[T]=0,需要注意的是,由于从某顶点出发可能会无法到达终点 T,因此此时 dp 数组不能再全部初始化为 0,比较合适的做法是将 dp 初始化为一个极大的负数(-INF),来表达无法到达终点,然后设置一个 vis 数组来表示顶点是否已被访问

int vis[N];
int G[N][N];
int dp[N];//使用前初始化为-INF,且终点dp[T]=0
int DP(int i) {
    if(vis[i]) 
        return dp[i];
    vis[i]=true;
    for(int j=0; j<n; j++) { //遍历i的所有可达出边
        if(G[i][j]!=INF) {
            dp[i]=max(dp[i],DP(j)+G[i][j]);
        }
    }
    return dp[i];
}

【例题】

  • Find the safest road(HDU-1596)(Floyd 变形求最长路)点击这里
  • 矩形嵌套(NYOJ-16)(dp 求最长路)点击这里
  • Skiing(2017 ACM-ICPC 亚洲区(乌鲁木齐赛区)网络赛 H)(拓扑排序求最长路)点击这里
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值