Maximum Profit from Valid Topological Order in DAG
Problem
You are given a Directed Acyclic Graph (DAG) with n nodes labeled from
0 to n - 1, represented by a 2D array edges, where edges[i] = [ui, vi]
indicates a directed edge from node ui to vi. Each node has an associated
score given in an array score, where score[i] represents the score of node i.
You must process the nodes in a valid topological order. Each node is assigned a 1-based position in the processing order.
The profit is calculated by summing up the product of each node's score and its position in the ordering.
Return the maximum possible profit achievable with an optimal topological order.
A topological order of a DAG is a linear ordering of its nodes such that for every directed edge u -> v, node u comes before v in the ordering.
Examples
Example 1
Input: n = 2, edges = [[0,1]], score = [2,3]
Output: 8
Explanation:

Node 1 depends on node 0, so a valid order is `[0, 1]`.
Node | Processing Order | Score | Multiplier | Profit Calculation
---|---|---|---|---
0 | 1st | 2 | 1 | 2 × 1 = 2
1 | 2nd | 3 | 2 | 3 × 2 = 6
The maximum total profit achievable over all valid topological orders is `2 +
6 = 8`.
Example 2
Input: n = 3, edges = [[0,1],[0,2]], score = [1,6,3]
Output: 25
Explanation:

Nodes 1 and 2 depend on node 0, so the most optimal valid order is `[0, 2,
1]`.
Node | Processing Order | Score | Multiplier | Profit Calculation
---|---|---|---|---
0 | 1st | 1 | 1 | 1 × 1 = 1
2 | 2nd | 3 | 2 | 3 × 2 = 6
1 | 3rd | 6 | 3 | 6 × 3 = 18
The maximum total profit achievable over all valid topological orders is `1 +
6 + 18 = 25`.
Constraints
1 <= n == score.length <= 221 <= score[i] <= 10^50 <= edges.length <= n * (n - 1) / 2edges[i] == [ui, vi]denotes a directed edge fromuitovi.0 <= ui, vi < nui != vi- The input graph is guaranteed to be a DAG.
- There are no duplicate edges.
Solution
Method 1 – Bitmask Dynamic Programming (DP over Subsets)
Intuition
Since the number of nodes n is small (≤ 22), we can use bitmask DP to try all possible valid topological orders efficiently. For each subset of nodes, we keep track of the maximum profit achievable by processing those nodes in some valid order.
Approach
- For each node, compute its dependencies as a bitmask (which nodes must come before it).
- Let
dp[mask]be the maximum profit for the subset of nodes represented bymask. - For each subset
mask, try to add a nodeunot inmaskwhose dependencies are all satisfied (i.e., all its dependencies are inmask). - When adding node
uas the next node, its position ispopcount(mask) + 1. - Update
dp[mask | (1 << u)]with the new profit. - The answer is
max(dp[mask])for all masks with all nodes included.
Code
C++
class Solution {
public:
int maximumProfit(int n, vector<vector<int>>& edges, vector<int>& score) {
vector<int> dep(n, 0);
for (auto& e : edges) dep[e[1]] |= 1 << e[0];
int N = 1 << n;
vector<int> dp(N, INT_MIN);
dp[0] = 0;
for (int mask = 0; mask < N; ++mask) {
int pos = __builtin_popcount(mask) + 1;
for (int u = 0; u < n; ++u) {
if (!(mask & (1 << u)) && (dep[u] & mask) == dep[u]) {
int nmask = mask | (1 << u);
dp[nmask] = max(dp[nmask], dp[mask] + score[u] * pos);
}
}
}
return *max_element(dp.begin(), dp.end());
}
};
Go
func maximumProfit(n int, edges [][]int, score []int) int {
dep := make([]int, n)
for _, e := range edges {
dep[e[1]] |= 1 << e[0]
}
N := 1 << n
dp := make([]int, N)
for i := range dp {
dp[i] = -1 << 30
}
dp[0] = 0
for mask := 0; mask < N; mask++ {
pos := bits.OnesCount(uint(mask)) + 1
for u := 0; u < n; u++ {
if mask&(1<<u) == 0 && (dep[u]&mask) == dep[u] {
nmask := mask | (1 << u)
if dp[mask]+score[u]*pos > dp[nmask] {
dp[nmask] = dp[mask] + score[u]*pos
}
}
}
}
ans := 0
for _, v := range dp {
if v > ans {
ans = v
}
}
return ans
}
Java
class Solution {
public int maximumProfit(int n, int[][] edges, int[] score) {
int[] dep = new int[n];
for (int[] e : edges) dep[e[1]] |= 1 << e[0];
int N = 1 << n;
int[] dp = new int[N];
Arrays.fill(dp, Integer.MIN_VALUE);
dp[0] = 0;
for (int mask = 0; mask < N; mask++) {
int pos = Integer.bitCount(mask) + 1;
for (int u = 0; u < n; u++) {
if ((mask & (1 << u)) == 0 && (dep[u] & mask) == dep[u]) {
int nmask = mask | (1 << u);
dp[nmask] = Math.max(dp[nmask], dp[mask] + score[u] * pos);
}
}
}
int ans = 0;
for (int v : dp) ans = Math.max(ans, v);
return ans;
}
}
Kotlin
class Solution {
fun maximumProfit(n: Int, edges: Array<IntArray>, score: IntArray): Int {
val dep = IntArray(n)
for (e in edges) dep[e[1]] = dep[e[1]] or (1 shl e[0])
val N = 1 shl n
val dp = IntArray(N) { Int.MIN_VALUE }
dp[0] = 0
for (mask in 0 until N) {
val pos = mask.countOneBits() + 1
for (u in 0 until n) {
if ((mask and (1 shl u)) == 0 && (dep[u] and mask) == dep[u]) {
val nmask = mask or (1 shl u)
dp[nmask] = maxOf(dp[nmask], dp[mask] + score[u] * pos)
}
}
}
return dp.maxOrNull() ?: 0
}
}
Python
class Solution:
def maximumProfit(self, n: int, edges: list[list[int]], score: list[int]) -> int:
dep = [0] * n
for u, v in edges:
dep[v] |= 1 << u
N = 1 << n
dp = [float('-inf')] * N
dp[0] = 0
for mask in range(N):
pos = bin(mask).count('1') + 1
for u in range(n):
if not (mask & (1 << u)) and (dep[u] & mask) == dep[u]:
nmask = mask | (1 << u)
dp[nmask] = max(dp[nmask], dp[mask] + score[u] * pos)
return max(dp)
Rust
impl Solution {
pub fn maximum_profit(n: i32, edges: Vec<Vec<i32>>, score: Vec<i32>) -> i32 {
let n = n as usize;
let mut dep = vec![0; n];
for e in &edges {
dep[e[1] as usize] |= 1 << e[0];
}
let N = 1 << n;
let mut dp = vec![i32::MIN; N];
dp[0] = 0;
for mask in 0..N {
let pos = mask.count_ones() + 1;
for u in 0..n {
if (mask & (1 << u)) == 0 && (dep[u] & mask) == dep[u] {
let nmask = mask | (1 << u);
dp[nmask] = dp[nmask].max(dp[mask] + score[u] * pos as i32);
}
}
}
*dp.iter().max().unwrap()
}
}
TypeScript
class Solution {
maximumProfit(n: number, edges: number[][], score: number[]): number {
const dep: number[] = Array(n).fill(0);
for (const [u, v] of edges) dep[v] |= 1 << u;
const N = 1 << n;
const dp: number[] = Array(N).fill(-Infinity);
dp[0] = 0;
for (let mask = 0; mask < N; mask++) {
const pos = mask.toString(2).split('1').length - 1 + 1;
for (let u = 0; u < n; u++) {
if ((mask & (1 << u)) === 0 && (dep[u] & mask) === dep[u]) {
const nmask = mask | (1 << u);
dp[nmask] = Math.max(dp[nmask], dp[mask] + score[u] * pos);
}
}
}
return Math.max(...dp);
}
}
Complexity
- ⏰ Time complexity:
O(n^2 * 2^n), since for each subset (2^n), we check n nodes and each check is O(n) for bit operations. - 🧺 Space complexity:
O(2^n), for the DP array.