backport graph from main

This commit is contained in:
Corentin Le Molgat
2024-07-12 13:55:33 +02:00
parent c974ff4c07
commit 7b35c765f9
32 changed files with 2529 additions and 117 deletions

View File

@@ -37,6 +37,16 @@ code_sample_cc(name = "dag_shortest_path_sequential")
code_sample_cc(name = "dag_simple_shortest_path")
code_sample_cc(name = "dag_multiple_shortest_paths_one_to_all")
code_sample_cc(name = "dag_multiple_shortest_paths_sequential")
code_sample_cc(name = "dag_simple_multiple_shortest_paths")
code_sample_cc(name = "dag_constrained_shortest_path_sequential")
code_sample_cc(name = "dag_simple_constrained_shortest_path")
code_sample_cc(name = "dijkstra_all_pairs_shortest_paths")
code_sample_cc(name = "dijkstra_directed")
@@ -47,6 +57,10 @@ code_sample_cc(name = "dijkstra_sequential")
code_sample_cc(name = "dijkstra_undirected")
code_sample_cc(name = "root_a_tree")
code_sample_cc(name = "rooted_tree_paths")
code_sample_java(name = "SimpleMaxFlowProgram")
code_sample_cc_py(name = "simple_max_flow_program")

View File

@@ -14,6 +14,7 @@
"""Helper macro to compile and test code samples."""
load("@pip_deps//:requirements.bzl", "requirement")
load("@rules_python//python:defs.bzl", "py_binary", "py_test")
def code_sample_cc(name):
native.cc_binary(
@@ -26,11 +27,13 @@ def code_sample_cc(name):
"//ortools/graph:assignment",
"//ortools/graph:bounded_dijkstra",
"//ortools/graph:bfs",
"//ortools/graph:dag_constrained_shortest_path",
"//ortools/graph:dag_shortest_path",
"//ortools/graph:ebert_graph",
"//ortools/graph:linear_assignment",
"//ortools/graph:max_flow",
"//ortools/graph:min_cost_flow",
"//ortools/graph:rooted_tree",
"@com_google_absl//absl/random",
],
)
@@ -47,17 +50,19 @@ def code_sample_cc(name):
"//ortools/graph:assignment",
"//ortools/graph:bounded_dijkstra",
"//ortools/graph:bfs",
"//ortools/graph:dag_constrained_shortest_path",
"//ortools/graph:dag_shortest_path",
"//ortools/graph:ebert_graph",
"//ortools/graph:linear_assignment",
"//ortools/graph:max_flow",
"//ortools/graph:min_cost_flow",
"//ortools/graph:rooted_tree",
"@com_google_absl//absl/random",
],
)
def code_sample_py(name):
native.py_binary(
py_binary(
name = name + "_py3",
srcs = [name + ".py"],
main = name + ".py",
@@ -72,7 +77,7 @@ def code_sample_py(name):
srcs_version = "PY3",
)
native.py_test(
py_test(
name = name + "_py_test",
size = "small",
srcs = [name + ".py"],

View File

@@ -0,0 +1,138 @@
// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// [START imports]
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_constrained_shortest_path.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
// [END imports]
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// [START graph]
// Create a graph with n + 2 nodes, indexed from 0:
// * Node n is `source`
// * Node n+1 is `dest`
// * Nodes M = [0, 1, ..., n-1] are in the middle.
//
// There is a single resource constraints with limit 1.
//
// The graph has 3 * n - 1 arcs (with weights and both resources):
// * (source -> i) with weight 100 and no resource use for i in M
// * (i -> dest) with weight 100 and no resource use for i in M
// * (i -> (i+1)) with weight 1 and resource use of 1 for i = 0, ..., n-2
//
// Every path [source, i, dest] for i in M is a constrained shortest path from
// source to dest with weight 200.
const int n = 5;
const int source = n;
const int dest = n + 1;
const int num_arcs = 3 * n - 1;
util::StaticGraph<> graph;
// There are 3 types of arcs: (1) source to M, (2) M to dest, and (3) within
// M. This vector stores all of them, first of type (1), then type (2),
// then type (3). The arcs are ordered by i in M within each type.
std::vector<double> weights(num_arcs);
// Resources are first indexed by resource, then by arc.
std::vector<std::vector<double>> resources(1, std::vector<double>(num_arcs));
for (int i = 0; i < n; ++i) {
graph.AddArc(source, i);
weights[i] = 100.0;
resources[0][i] = 0.0;
}
for (int i = 0; i < n; ++i) {
graph.AddArc(i, dest);
weights[n + i] = 100.0;
resources[0][n + i] = 0.0;
}
for (int i = 0; i + 1 < n; ++i) {
graph.AddArc(i, i + 1);
weights[2 * n + i] = 1.0;
resources[0][2 * n + i] = 1.0;
}
// Static graph reorders the arcs at Build() time, use permutation to get from
// the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
util::Permute(permutation, &resources[0]);
// [END graph]
// [START first-path]
// A reusable shortest path calculator.
// We need a topological order. For this structured graph, we find it by hand
// instead of using util::graph::FastTopologicalSort().
std::vector<int32_t> topological_order = {source};
for (int32_t i = 0; i < n; ++i) {
topological_order.push_back(i);
}
topological_order.push_back(dest);
const std::vector<int> sources = {source};
const std::vector<int> destinations = {dest};
const std::vector<double> max_resources = {1.0};
operations_research::ConstrainedShortestPathsOnDagWrapper<util::StaticGraph<>>
constrained_shortest_path_on_dag(&graph, &weights, &resources,
topological_order, sources, destinations,
&max_resources);
operations_research::PathWithLength initial_constrained_shortest_path =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
std::cout << "Initial distance: " << initial_constrained_shortest_path.length
<< std::endl;
std::cout << "Initial path: "
<< absl::StrJoin(initial_constrained_shortest_path.node_path, ", ")
<< std::endl;
// [END first-path]
// [START more-paths]
// Now, we make a single arc from source to M free, and a single arc from M
// to dest free, and resolve. If the free edge from the source hits before
// the free edge to the dest in M, we use both, walking through M. Otherwise,
// we use only one free arc.
std::vector<std::pair<int, int>> fast_paths = {{2, 3}, {8, 1}, {3, 7}};
for (const auto [free_from_source, free_to_dest] : fast_paths) {
weights[permutation[free_from_source]] = 0;
weights[permutation[n + free_to_dest]] = 0;
operations_research::PathWithLength constrained_shortest_path =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
std::cout << "source -> " << free_from_source << " and " << free_to_dest
<< " -> dest are now free" << std::endl;
std::string label = absl::StrCat("_", free_from_source, "_", free_to_dest);
std::cout << "Distance" << label << ": " << constrained_shortest_path.length
<< std::endl;
std::cout << "Path" << label << ": "
<< absl::StrJoin(constrained_shortest_path.node_path, ", ")
<< std::endl;
// Restore the old weights
weights[permutation[free_from_source]] = 100;
weights[permutation[n + free_to_dest]] = 100;
}
// [END more-paths]
return 0;
}

View File

@@ -0,0 +1,87 @@
// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cstdint>
#include <iostream>
#include <vector>
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/base/status_macros.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/topologicalsorter.h"
namespace {
absl::Status Main() {
util::StaticGraph<> graph;
std::vector<double> weights;
graph.AddArc(0, 1);
weights.push_back(2.0);
graph.AddArc(0, 2);
weights.push_back(5.0);
graph.AddArc(1, 4);
weights.push_back(1.0);
graph.AddArc(2, 4);
weights.push_back(-3.0);
graph.AddArc(3, 4);
weights.push_back(0.0);
// Static graph reorders the arcs at Build() time, use permutation to get
// from the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
// We need a topological order. We can find it by hand on this small graph,
// e.g., {0, 1, 2, 3, 4}, but we demonstrate how to compute one instead.
ASSIGN_OR_RETURN(const std::vector<int32_t> topological_order,
util::graph::FastTopologicalSort(graph));
operations_research::KShortestPathsOnDagWrapper<util::StaticGraph<>>
shortest_paths_on_dag(&graph, &weights, topological_order,
/*path_count=*/2);
const int source = 0;
shortest_paths_on_dag.RunKShortestPathOnDag({source});
// For each node other than 0, print its distance and the shortest path.
for (int node = 1; node < 5; ++node) {
std::cout << "Node " << node << ":\n";
if (!shortest_paths_on_dag.IsReachable(node)) {
std::cout << "\tNo path to node " << node << std::endl;
continue;
}
const std::vector<double> lengths = shortest_paths_on_dag.LengthsTo(node);
const std::vector<std::vector<int32_t>> paths =
shortest_paths_on_dag.NodePathsTo(node);
for (int path_index = 0; path_index < lengths.size(); ++path_index) {
std::cout << "\t#" << (path_index + 1) << " shortest path to node "
<< node << " has length: " << lengths[path_index] << std::endl;
std::cout << "\t#" << (path_index + 1) << " shortest path to node "
<< node << " is: " << absl::StrJoin(paths[path_index], ", ")
<< std::endl;
}
}
return absl::OkStatus();
}
} // namespace
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
QCHECK_OK(Main());
return 0;
}

View File

@@ -0,0 +1,135 @@
// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// [START imports]
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
// [END imports]
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// [START graph]
// Create a graph with n + 2 nodes, indexed from 0:
// * Node n is `source`
// * Node n+1 is `dest`
// * Nodes M = [0, 1, ..., n-1] are in the middle.
//
// The graph has 3 * n - 1 arcs (with weights):
// * (source -> i) with weight 100 + i for i in M
// * (i -> dest) with weight 100 + i for i in M
// * (i -> (i+1)) with weight 10 for i = 0, ..., n-2
const int n = 10;
const int source = n;
const int dest = n + 1;
util::StaticGraph<> graph;
// There are 3 types of arcs: (1) source to M, (2) M to dest, and (3) within
// M. This vector stores all of them, first of type (1), then type (2),
// then type (3). The arcs are ordered by i in M within each type.
std::vector<double> weights(3 * n - 1);
for (int i = 0; i < n; ++i) {
graph.AddArc(source, i);
weights[i] = 100.0 + i;
}
for (int i = 0; i < n; ++i) {
graph.AddArc(i, dest);
weights[n + i] = 100.0 + i;
}
for (int i = 0; i + 1 < n; ++i) {
graph.AddArc(i, i + 1);
weights[2 * n + i] = 10.0;
}
// Static graph reorders the arcs at Build() time, use permutation to get from
// the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
// [END graph]
// [START first-path]
// A reusable shortest path calculator.
// We need a topological order. For this structured graph, we find it by hand
// instead of using util::graph::FastTopologicalSort().
std::vector<int32_t> topological_order = {source};
for (int32_t i = 0; i < n; ++i) {
topological_order.push_back(i);
}
topological_order.push_back(dest);
operations_research::KShortestPathsOnDagWrapper<util::StaticGraph<>>
shortest_paths_on_dag(&graph, &weights, topological_order,
/*path_count=*/2);
shortest_paths_on_dag.RunKShortestPathOnDag({source});
const std::vector<double> initial_lengths =
shortest_paths_on_dag.LengthsTo(dest);
const std::vector<std::vector<int32_t>> initial_paths =
shortest_paths_on_dag.NodePathsTo(dest);
std::cout << "No free arcs" << std::endl;
for (int path_index = 0; path_index < initial_lengths.size(); ++path_index) {
std::cout << "\t#" << (path_index + 1)
<< " shortest path has length: " << initial_lengths[path_index]
<< std::endl;
std::cout << "\t#" << (path_index + 1) << " shortest path is: "
<< absl::StrJoin(initial_paths[path_index], ", ") << std::endl;
}
// [END first-path]
// [START more-paths]
// Now, we make a single arc from source to M free, and a single arc from M
// to dest free, and resolve. If the free edge from the source hits before
// the free edge to the dest in M, we use both, walking through M. Otherwise,
// we use only one free arc.
std::vector<std::pair<int, int>> fast_paths = {
{2, 4}, {8, 1}, {3, 3}, {0, 0}};
for (const auto [free_from_source, free_to_dest] : fast_paths) {
weights[permutation[free_from_source]] = 0;
weights[permutation[n + free_to_dest]] = 0;
shortest_paths_on_dag.RunKShortestPathOnDag({source});
std::cout << "source -> " << free_from_source << " and " << free_to_dest
<< " -> dest are now free" << std::endl;
std::string label =
absl::StrCat(" (", free_from_source, ", ", free_to_dest, ")");
const std::vector<double> lengths = shortest_paths_on_dag.LengthsTo(dest);
const std::vector<std::vector<int32_t>> paths =
shortest_paths_on_dag.NodePathsTo(dest);
for (int path_index = 0; path_index < lengths.size(); ++path_index) {
std::cout << "\t#" << (path_index + 1) << " shortest path" << label
<< " has length: " << lengths[path_index] << std::endl;
std::cout << "\t#" << (path_index + 1) << " shortest path" << label
<< " is: " << absl::StrJoin(paths[path_index], ", ")
<< std::endl;
}
// Restore the old weights
weights[permutation[free_from_source]] = 100 + free_from_source;
weights[permutation[n + free_to_dest]] = 100 + free_to_dest;
}
// [END more-paths]
return 0;
}

View File

@@ -0,0 +1,47 @@
// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <iostream>
#include <vector>
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_constrained_shortest_path.h"
#include "ortools/graph/dag_shortest_path.h"
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// The input graph, encoded as a list of arcs with distances.
std::vector<operations_research::ArcWithLengthAndResources> arcs = {
{.from = 0, .to = 1, .length = 5, .resources = {1, 2}},
{.from = 0, .to = 2, .length = 4, .resources = {3, 2}},
{.from = 0, .to = 2, .length = 1, .resources = {2, 3}},
{.from = 1, .to = 3, .length = -3, .resources = {8, 0}},
{.from = 2, .to = 3, .length = 0, .resources = {3, 1}}};
const int num_nodes = 4;
const std::vector<double> max_resources = {6, 3};
const int source = 0;
const int destination = 3;
const operations_research::PathWithLength path_with_length =
operations_research::ConstrainedShortestPathsOnDag(
num_nodes, arcs, source, destination, max_resources);
// Print to length of the path and then the nodes in the path.
std::cout << "Constrained shortest path length: " << path_with_length.length
<< std::endl;
std::cout << "Constrained shortest path nodes: "
<< absl::StrJoin(path_with_length.node_path, ", ") << std::endl;
return 0;
}

View File

@@ -0,0 +1,47 @@
// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <iostream>
#include <vector>
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_shortest_path.h"
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// The input graph, encoded as a list of arcs with distances.
std::vector<operations_research::ArcWithLength> arcs = {
{.from = 0, .to = 1, .length = 2}, {.from = 0, .to = 2, .length = 5},
{.from = 0, .to = 3, .length = 4}, {.from = 1, .to = 4, .length = 1},
{.from = 2, .to = 4, .length = -3}, {.from = 3, .to = 4, .length = 0}};
const int num_nodes = 5;
const int source = 0;
const int destination = 4;
const int path_count = 2;
const std::vector<operations_research::PathWithLength> paths_with_length =
operations_research::KShortestPathsOnDag(num_nodes, arcs, source,
destination, path_count);
for (int path_index = 0; path_index < paths_with_length.size();
++path_index) {
std::cout << "#" << (path_index + 1) << " shortest path has length: "
<< paths_with_length[path_index].length << std::endl;
std::cout << "#" << (path_index + 1) << " shortest path is: "
<< absl::StrJoin(paths_with_length[path_index].node_path, ", ")
<< std::endl;
}
return 0;
}

View File

@@ -23,11 +23,11 @@ int main(int argc, char** argv) {
// The input graph, encoded as a list of arcs with distances.
std::vector<operations_research::ArcWithLength> arcs = {
{.tail = 0, .head = 2, .length = 5},
{.tail = 0, .head = 3, .length = 4},
{.tail = 1, .head = 3, .length = 1},
{.tail = 2, .head = 4, .length = -3},
{.tail = 3, .head = 4, .length = 0}};
{.from = 0, .to = 2, .length = 5},
{.from = 0, .to = 3, .length = 4},
{.from = 1, .to = 3, .length = 1},
{.from = 2, .to = 4, .length = -3},
{.from = 3, .to = 4, .length = 0}};
const int num_nodes = 5;
const int source = 0;

View File

@@ -0,0 +1,86 @@
// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cstdint>
#include <iostream>
#include <utility>
#include <vector>
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/base/status_macros.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/rooted_tree.h"
namespace {
absl::Status Main() {
// Make an undirected tree as a graph using ListGraph (add the arcs in each
// direction).
const int32_t num_nodes = 5;
std::vector<std::pair<int32_t, int32_t>> arcs = {
{0, 1}, {1, 2}, {2, 3}, {1, 4}};
util::ListGraph<> graph(num_nodes, 2 * static_cast<int32_t>(arcs.size()));
for (const auto [s, t] : arcs) {
graph.AddArc(s, t);
graph.AddArc(t, s);
}
// Root the tree from 2. Save the depth of each node and topological ordering
int root = 2;
std::vector<int32_t> topological_order;
std::vector<int32_t> depth;
ASSIGN_OR_RETURN(const operations_research::RootedTree<int32_t> tree,
operations_research::RootedTreeFromGraph(
root, graph, &topological_order, &depth));
// Parents are:
// 0 -> 1
// 1 -> 2
// 2 is root (returns -1)
// 3 -> 2
// 4 -> 1
std::cout << "Parents:" << std::endl;
for (int i = 0; i < num_nodes; ++i) {
std::cout << " " << i << " -> " << tree.parents()[i] << std::endl;
}
// Depths are:
// 0: 2
// 1: 1
// 2: 0
// 3: 1
// 4: 2
std::cout << "Depths:" << std::endl;
for (int i = 0; i < num_nodes; ++i) {
std::cout << " " << i << " -> " << depth[i] << std::endl;
}
// Many possible topological orders, including:
// [2, 1, 0, 4, 3]
// all starting with 2.
std::cout << "Topological order: " << absl::StrJoin(topological_order, ", ")
<< std::endl;
return absl::OkStatus();
}
} // namespace
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
QCHECK_OK(Main());
return 0;
}

View File

@@ -0,0 +1,58 @@
// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <iostream>
#include <vector>
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/base/status_macros.h"
#include "ortools/graph/rooted_tree.h"
namespace {
absl::Status Main() {
// Make an rooted tree on 5 nodes with root 2 and the parental args:
// 0 -> 1
// 1 -> 2
// 2 is root
// 3 -> 2
// 4 -> 1
ASSIGN_OR_RETURN(
const operations_research::RootedTree<int> tree,
operations_research::RootedTree<int>::Create(2, {1, 2, -1, 2, 1}));
// Precompute this for LCA computations below.
const std::vector<int> depths = tree.AllDepths();
// Find the path between every pair of nodes in the tree.
for (int s = 0; s < 5; ++s) {
for (int t = 0; t < 5; ++t) {
int lca = tree.LowestCommonAncestorByDepth(s, t, depths);
const std::vector<int> path = tree.Path(s, t, lca);
std::cout << s << " -> " << t << " [" << absl::StrJoin(path, ", ") << "]"
<< std::endl;
}
}
return absl::OkStatus();
}
} // namespace
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
QCHECK_OK(Main());
return 0;
}