DAG最长路
DAG指的是有向无环图, 求它的最长路径有一种DP方法.
我们建立一个数组dp[n+1], dp[i]代表从这个结点出发的最长路径, 那么要求出dp[i], 我们只要求出从i出发的下一结点的dp[j], 使得dp[i] = max(dp[i], dp[j] + G[i][j]), 这就是它的状态转移方程.
可是dp[j] 怎么求呢? 同理,我们如果求出它的后一个结点的dp[k], 我们就可以相应求出dp[j].
经过上面的分析, 不难发现, 整个的过程可以用递归实现. 当i结点的出度为0时, 我们就可以给出dp[i] = 0, 从而实现整个问题的求解.
并且要注意的是, 只对一个节点进行递归求解是不行的, 因为有其他入度为0的结点将不会被考虑进去. 我的理解是, 找出所有入度为0的结点i, 然后solve(i)即可.
完整代码如下
//DAG最短路问题, 动态规划算法, 所有路径里面最长的
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 105;
const int INF = 0x3f3f3f3f;
int G[maxn][maxn] = {};
int dp[maxn] = {}, n, in[maxn] = {}; // 作为结果的数组
int solve(int id)
{
if (dp[id] > 0) return dp[id];
for (int i = 1; i <= n; ++i) {
if (G[id][i] != INF) dp[id] = max(dp[id], solve(i) + G[id][i]);
}
return dp[id];
}
int main()
{
memset(G, 0x3f, sizeof(G));
int m, x, y, z;
cin >> n >> m;
for (int i = 0; i < m; ++i) {
cin >> x >> y >> z;
G[x][y] = z; // 存好图
in[y]++; // 入度
}
for (int i = 1; i <= n; ++i) {
if (in[i] == 0) solve(i); // 不必所有的, 只需找到入度为0 的结点.
}
for (int i = 1; i <= n; ++i) {
cout << i << ':' << dp[i] << '\n';
}
cout << endl;
}
/*
9 12
1 3 2
3 6 3
6 8 2
1 4 2
4 6 3
6 9 3
2 4 3
4 7 2
2 5 2
5 7 1
7 9 2
7 8 2
9
*/
DAG最长路径
上面的代码我们可以求得最长路径的值, 但是我们想要求解路径该怎么办呢? 只要稍微改动一点, 在dp的过程中存储下一个结点, 最后获取路径即可.
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 105;
const int INF = 0x3f3f3f3f;
int G[maxn][maxn], n, choice[maxn] = {};
int dp[maxn] = {}, in[maxn] = {};
int solve(int i)
{
if (dp[i]) return dp[i];
for (int j = 1; j <= n; ++j) {
if (G[i][j] != INF) {
if (dp[i] < solve(j) + G[i][j]) {
dp[i] = solve(j) + G[i][j];
choice[i] = j;
}
}
}
return dp[i];
}
void getPath(int k)
{
cout << "DAG最长路 : " << dp[k] << endl << "路径为: ";
while (k != 0) {
cout << k << " ";
k = choice[k];
}
cout << endl;
}
int main()
{
memset(G, 0x3f, sizeof(G));
int m, x, y, z, it = -1;
cin >> n >> m;
for (int i = 0; i < m; ++i) {
cin >> x >> y >> z;
in[y] ++;
G[x][y] = z;
}
for (int i = 1; i <= n; ++i) {
if (!in[i]) solve(i);
}
for (int i = 1; i <= n; ++i) {
if (dp[0] < dp[i]) {
it = i;
dp[0] = dp[i];
}
}
getPath(it);
}
/*
9 12
1 3 2
3 6 3
6 8 2
1 4 2
4 6 3
6 9 3
2 4 3
4 7 2
2 5 2
5 7 1
7 9 2
7 8 2
*/
以i为终点的最长路径
上面两个都是以i为出发点的最长路径, 如果是以i为结尾的最长路径呢? 其实也是差不多的.
#include <iostream>
#include <cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 105;
int G[maxn][maxn], n, last[maxn] = {};
int out[maxn] = {}, dp[maxn], vis[maxn] = {};
int solve(int i)
{
if (vis[i]) return dp[i];
vis[i] = true;
for (int j = 1; j <= n; ++j) {
if (G[j][i] != INF) {
if (dp[i] < G[j][i] + solve(j)) {
dp[i] = G[j][i] + solve(j);
last[i] = j;
}
}
}
return dp[i];
}
void getPath(int k)
{
cout << "DAG最长路长: " << dp[k] << "\n路径: ";
while (k != 0) {
cout << k << ' ';
k = last[k];
}
}
int main()
{
memset(G, 0x3f, sizeof(G));
memset(dp, 0x3f, sizeof(dp));
for (int i = 0; i < maxn; ++i)
dp[i] = 0;
int m, x, y, z, k = 0;
cin >> n >> m;
for (int i = 1; i <= m; ++i) {
cin >> x >> y >> z;
G[x][y] = z;
out[y] ++;
}
for (int i = 1; i <= n; ++i) {
if (out[i]) solve(i);
}
for (int i = 1; i <= n; ++i) {
if (dp[i] > dp[0]) {
dp[0] = dp[i];
k = i;
}
}
cout << "k = " << k << endl;
for (int i = 1; i <= n; ++i) {
cout << i << " : " << dp[i] << endl;
}
getPath(k);
}
/*
9 12
1 3 2
3 6 3
6 8 2
1 4 2
4 6 3
6 9 3
2 4 3
4 7 2
2 5 2
5 7 1
7 9 2
7 8 2
*/
关键路径
求DAG最长路也可以用关键路径的方法求解.
其核心思路是:
有两组数组, 分别为ve[n], 节点的最早时间, vl[n]结点的最晚时间.
e[n], 边的最早时间, l[n], 边的最晚时间.
在实际应用中结点代表事件, 而边代表活动. 活动是需要时间的.
ve和e的关系是: e[i] = e[j], j为与i指向的下一个结点.
vl和l的关系是: l[i] = l[j] - w[i][j], j为i指向的下一个结点.
当e[i] 和 v[i]相等, 那么i就是关键路径上的结点.
这种关键路径可以有很多条, 最后可以使用dfs遍历出来. 不过我写的代码这个过程不知道那里出错了…
用这种关键路径的方法求最长路相比于动态规划方法比较麻烦. 不过其实求最长路的长度, 只是这个方法中的前一部分就可以求解了.
#include <iostream>
#include <cstring>
#include <stack>
#include <vector>
#include <queue>
using namespace std;
struct Node {
int v, w; // v means the next node, and w means the value of the edge
Node (int y, int z) : v(y), w(z){}
};
// how to initialize these arries?
const int maxn = 105, INF = 0x3f3f3f3f;
int ve[maxn] = {}, vl[maxn], n;
int e[maxn], l[maxn], inDeg[maxn] = {};
vector<Node> G[maxn];
stack<int> topOrder; // the opposite topology order of stack
queue<int> topSort; // the topology order of queue
vector<int> path[maxn]; // dfs the path
void getVe()
{
memset(ve, 0, sizeof(ve)); // initialize to zero
for (int i = 0; i < n; ++i) {
if (inDeg[i] == 0) {
topSort.push(i);
topOrder.push(i);
}
}
while (!topSort.empty()) {
int d = topSort.front();
topSort.pop();
for (int i = 0; i < G[d].size(); ++i) {
int v = G[d][i].v;
ve[v] = max(ve[v], ve[d] + G[d][i].w);
cout << "v = " << v << " ve[v] = " << ve[v] << endl;
inDeg[v]--;
if (inDeg[v] == 0) {
topSort.push(v);
topOrder.push(v);
}
}
}
}
void getVl()
{
while (!topOrder.empty()) {
int d = topOrder.top();
topOrder.pop();
for (int i = 0; i < G[d].size(); ++i) {
int v = G[d][i].v;
vl[d] = min(vl[d], vl[v] - G[d][i].w);
}
}
}
void getImport() // get critical path
{
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < G[i].size(); ++j) {
int v = G[i][j].v;
int e = ve[i], l = vl[v] - G[i][j].w;
if (e == l) path[i].push_back(v);
}
}
}
void dfs(int u)
{
bool flag = 0;
for (int i = 0; i < G[u].size(); ++i) {
//if (u == 1) cout << "u == 1\n";
int v = path[u][i];
flag = 1;
cout << u << "--->";
dfs(v);
}
if (flag == 0) cout << u << endl;
}
int main()
{
int x, y, z, m, ans = 0;
cin >> n >> m;
for (int i = 0; i < m; ++i) {
cin >> x >> y >> z;
G[x].push_back(Node(y, z)); // save the graphic
inDeg[y]++; // in degrees
}
getVe();
cout << "ve[1...n] = ";
for (int i = 1; i <= n; ++i) {
ans = max(ans, ve[i]);
cout << ve[i] << ' ';
}
cout << endl;
cout << "最长路径为 " << ans << endl;
fill(vl, vl + n + 1, ans);
getVl();
cout << "vl[1...n] = ";
for (int i = 1; i <= n; ++i) {
cout << vl[i] << ' ';
}
cout << endl;
getImport();
/*for (int i = 1; i <= n; ++i) {
if (vl[i] == 0 && ve[i] == 0) dfs(i);
}*/
cout << "test: \n";
for (int i = 1; i <= n; ++i) {
cout << "id = " << i << " : ";
cout << path[i].size();
cout << endl;
}
dfs(1); // 不知道最后遍历关键路径图的过程中出了什么问题
}
/*
6 6
1 2 30
1 4 40
2 3 50
4 5 80
5 6 20
3 5 70
6 6
1 2 30
1 4 70
2 3 50
4 5 80
5 6 20
3 5 70
*/