Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions content/graphs/chu-liu.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
*Description:* Minimum spanning arborescence (directed MST), Edmonds/Chu-Liu algorithm
*Complexity:* $O(n m)$, returns -1 if not reachable
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description mentions returning -1 if "not reachable", but it would be clearer to specify "returns -1 if not all nodes are reachable from the root" or "returns -1 if no spanning arborescence exists rooted at the given root node". The current phrasing is somewhat ambiguous about what needs to be reachable from what.

Suggested change
*Complexity:* $O(n m)$, returns -1 if not reachable
*Complexity:* $O(n m)$, returns -1 if not all nodes are reachable from the root (i.e., if no spanning arborescence exists rooted at the given root node)

Copilot uses AI. Check for mistakes.
*Status:* Tested
*/
template <class T> struct chu_liu {
struct edge {
int u, v, id;
T len;
};
int n;
vector<edge> e;
vector<int> inc, dec, take, pre, num, id, vis;
vector<T> inw;
T INF;

chu_liu(int n) : n(n), pre(n), num(n), id(n), vis(n), inw(n) {
INF = numeric_limits<T>::max() / 2;
}
void add_edge(int x, int y, T w) {
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no validation that the vertex indices x and y are within valid bounds [0, n). If invalid vertex indices are passed to add_edge, it could lead to out-of-bounds array access when these edges are processed in the solve function (e.g., at lines 32-33 when accessing inw[ed.v], pre[ed.v], etc.). Consider adding bounds checking or documenting that the caller must ensure vertices are valid.

Suggested change
void add_edge(int x, int y, T w) {
void add_edge(int x, int y, T w) {
// Ensure vertex indices are within valid bounds [0, n)
if (x < 0 || x >= n || y < 0 || y >= n) {
return;
}

Copilot uses AI. Check for mistakes.
inc.push_back(0), dec.push_back(0), take.push_back(0);
e.push_back({x, y, (int)e.size(), w});
}
T solve(int root) {
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no validation that the root parameter is within valid bounds [0, n). If an invalid root value is passed (e.g., negative or >= n), it could lead to out-of-bounds array access at line 34 (inw[root] = 0) and throughout the function. Consider adding a check or documenting that the caller must ensure root is valid.

Suggested change
T solve(int root) {
T solve(int root) {
if (root < 0 || root >= n)
return -1;

Copilot uses AI. Check for mistakes.
auto e2 = e;
T ans = 0;
int eg = e.size() - 1, pos = eg;
while (1) {
for (int i = 0; i < n; i++)
inw[i] = INF, id[i] = vis[i] = -1;
for (auto &ed : e2)
if (ed.len < inw[ed.v])
inw[ed.v] = ed.len, pre[ed.v] = ed.u, num[ed.v] = ed.id;
inw[root] = 0;
for (int i = 0; i < n; i++)
if (inw[i] == INF)
return -1;
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function returns -1 to indicate failure (line 37), but the return type is T (the template parameter for edge weights). If T is an unsigned type (e.g., unsigned int, unsigned long long), returning -1 will result in a large positive value instead of a negative sentinel value. Consider documenting that T must be a signed type, or use a different error handling mechanism such as std::optional or throwing an exception.

Copilot uses AI. Check for mistakes.
int tot = -1;
for (int i = 0; i < n; i++) {
ans += inw[i];
if (i != root)
take[num[i]]++;
int j = i;
while (vis[j] != i && j != root && id[j] < 0)
vis[j] = i, j = pre[j];
if (j != root && id[j] < 0) {
id[j] = ++tot;
for (int k = pre[j]; k != j; k = pre[k])
id[k] = tot;
}
}
if (tot < 0)
break;
for (int i = 0; i < n; i++)
if (id[i] < 0)
id[i] = ++tot;
n = tot + 1;
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modifying the member variable 'n' during algorithm execution (line 57) breaks the invariant that member vectors pre, num, id, vis, and inw are sized according to the original 'n' from the constructor. After this line, 'n' becomes the number of contracted nodes, but these vectors remain at their original size. While this happens to work because the algorithm only accesses indices within the new contracted node range, it makes the code fragile and could lead to bugs if the implementation is modified. Consider using a local variable for the contracted graph size instead.

Copilot uses AI. Check for mistakes.
int j = 0;
for (int i = 0; i < (int)e2.size(); i++) {
int v = e2[i].v;
e2[j] = {id[e2[i].u], id[e2[i].v], e2[i].id, e2[i].len - inw[v]};
if (e2[j].u != e2[j].v) {
inc.push_back(e2[i].id);
dec.push_back(num[v]);
take.push_back(0);
e2[j++].id = ++pos;
}
}
e2.resize(j);
root = id[root];
}
while (pos > eg) {
if (take[pos])
take[inc[pos]]++, take[dec[pos]]--;
pos--;
}
return ans;
}
};
5 changes: 3 additions & 2 deletions tracker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ content:
- 3-cycle.cpp
- chromatic-number.cpp
- undirected-euler-trail.cpp
- chu-liu.cpp
- geometry:
- point-2d.cpp
- convex-hull.cpp
Expand All @@ -154,5 +155,5 @@ content:
- simulated-annealing.cpp


- utilities:
- berlekamp-massey.py
- utilities:
- berlekamp-massey.py