public int estimateTimeOut(int[] from, int[] to, int[] len) {
        Integer l = from.length;

        int[] tmp = new int[l];
        for (int i = 0; i < l; ++i) {
            tmp[i] = to[i];
        }

        Arrays.sort(tmp);

        int idx;
        for (int i = 0; i < l; ++i) {
            idx = Arrays.binarySearch(tmp, to[i]);
            to[i] = idx + 1;

            if (from[i] != 0) {
                idx = Arrays.binarySearch(tmp, from[i]);
                from[i] = idx + 1;
            }
        }

        Integer[] tree = new Integer[l + 1];
        Integer[] costs = new Integer[l + 1];

        tree[0] = 0; // the root
        costs[0] = 0; // costs nothing to get to root
        for (Integer i = 1; i < l + 1; ++i) {
            tree[to[i - 1]] = from[i - 1];
            costs[to[i - 1]] = 2 * len[i - 1];
        }

        Integer max_cost = 0;
        for (Integer i = 1; i < l + 1; ++i) {
            Integer cur_node = i; // start from ancestor
            Integer cur_cost = 0;

            while (cur_node != 0) { // != 0 means `not root`
                cur_cost += costs[cur_node];
                cur_node = tree[cur_node];
            }

            if (cur_cost > max_cost)
                max_cost = cur_cost;
        }

        Integer result = 0;
        for (Integer i = 1; i < l + 1; ++i) {
            result += costs[i];
        }

        return result - max_cost / 2;
    }