📜  门| GATE CS 2020 |问题 21(1)

📅  最后修改于: 2023-12-03 15:28:38.600000             🧑  作者: Mango

门 | GATE CS 2020 |问题 21

这道题是2020年印度GATE计算机科学考试的一道题,是一道关于图论的问题。本题考察的是图的遍历方式之一——拓扑排序。下面让我们来看一下这道题的具体描述以及解决方案。

问题描述

给定一个由n个节点构成的有向无环图,每个节点i都有一个权重Wi。给定一个源节点S和一个目标节点D,从S到D的所有路径上的节点的权重和定义为该路径的贡献。在所有满足S到D路径存在的条件下的所有路径中,找出具有最大贡献的路径。如果具有最大贡献的路径不唯一,则输出其中任何一条即可。

输入格式

第1行包含两个整数n和m,分别表示节点数和边数。

接下来m行,每行包含三个整数u、v和w,表示从节点u到节点v有一条边,边权为w。

接下来一行包含n个整数,其中第i个整数表示节点i的权重Wi。

最后一行包含两个整数S和D,分别表示源节点和目标节点。

输出格式

输出具有最大贡献的路径上所有节点的编号,路径上的节点必须按拓扑排序后的顺序给出。

如果存在多条路径满足条件,则输出其中任何一条路径即可。

解决方案

对于这道题,我们首先需要为给定的有向无环图建立拓扑序。拓扑排序的方法有很多种,我们这里采用一种基于深度优先搜索(DFS)的方法。具体而言,我们首先从图中的任意一个节点开始,使用DFS访问每一个未被访问过的节点,并在最后一次访问该节点之后将其加入到拓扑序列中。

建立好拓扑序之后,我们可以根据拓扑序计算从S到D的具有最大贡献的路径。具体而言,我们可以定义dp[i]表示从S到i的具有最大贡献的路径的权值和。根据该定义,我们可以得到递推式如下:

dp[i] = max{dp[j] + Wi},其中(i, j)是图中的一条有向边。

递推式中的意义是,从S到i的具有最大贡献的路径,可以看作是从S到某个起点节点j,再加上从j到i的一条具有最大贡献的路径,其中j必须是i的祖先(即其拓扑序比i小)。对于拓扑序不合法的起点j,我们定义dp[j] = -INF(即负无穷),这样即可保证递推式成立。

最终,我们可以通过逆推dp数组,得到从S到D的具有最大贡献的路径。

下面是具体的实现代码(使用C++语言实现):

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const ll INF = 1e18;
const int MAXN = 100005;

vector<pair<int, ll>> G[MAXN];

int n, m, s, t;
ll w[MAXN];
int vis[MAXN], topo[MAXN];
ll dp[MAXN];

void dfs(int u, int& cur) {
    vis[u] = 1;
    for (auto& [v, w] : G[u]) {
        if (!vis[v]) {
            dfs(v, cur);
        }
    }
    topo[--cur] = u;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v;
        ll w;
        cin >> u >> v >> w;
        G[u].push_back({v, w});
    }
    for (int i = 1; i <= n; i++) {
        cin >> w[i];
    }
    cin >> s >> t;

    int cur = n + 1;
    for (int i = 1; i <= n; i++) {
        if (!vis[i]) {
            dfs(i, cur);
        }
    }

    fill(dp + 1, dp + n + 1, -INF);
    dp[s] = w[s];
    for (int i = 1; i <= n; i++) {
        int u = topo[i];
        for (auto& [v, ww] : G[u]) {
            dp[v] = max(dp[v], dp[u] + w[v]);
        }
    }

    vector<int> path;
    int u = t;
    while (u != s) {
        path.push_back(u);
        for (auto& [v, w] : G[u]) {
            if (dp[v] + w == dp[u]) {
                u = v;
                break;
            }
        }
    }
    path.push_back(s);

    reverse(path.begin(), path.end());
    for (auto& u : path) {
        cout << u << ' ';
    }
    cout << '\n';

    return 0;
}

其中,vis数组用于记录每个节点是否被访问过,topo数组用于保存拓扑序,dp数组用于保存每个节点的权值和等信息。使用vector<pair<int, ll>> G[MAXN]来存储有向图的邻接表,方便遍历每个节点相邻的边以及对应的边权。

这样,我们就成功解决了这道门 | GATE CS 2020 |问题 21。