Минимальное количество ребер для формирования пути длиной L

Я столкнулся с этой проблемой.

Учитывая взвешенное дерево T, найдите минимальное количество ребер, чтобы сформировать простой путь (без повторяющихся вершин или ребер) веса (суммы весов ребер) ровно L.


Более подробная информация:

L задается в качестве входных данных и может быть разной для каждого случая. В дереве N вершин, пронумерованных от 0 до N-1.

Моей первой мыслью было то, что лучшее, что я могу сделать, — это просмотреть все пути N^2 в T. Вот работоспособный код с примером входных данных.

import java.util.*;
class Edge {
    int toVertex, weight;
    Edge(int v, int w) {
        toVertex = v; weight = w;
    }
}
class Solver {
    // method called with the tree T given as adjacency list and the path length L to achieve
    // method to return minimum edges to create path of length L or -1 if impossible
    public static int solve(List<List<Edge>> T, long L) {
        int min = (int) 1e9;
        for (int i = 0; i < T.size(); i++) {
            min = Math.min(min, test(T, L, i, -1, 0, 0));
        }
        if (min == (int) 1e9) {
            return -1;
        } else {
            return min;
        }
    }
    static int test(List<List<Edge>> T, long L, int vertex, int parent, long length, int edges) {
        if (length == L) {
            return edges;
        } else if (length < L) {
            int min = (int) 1e9;
            for (Edge edge : T.get(vertex)) {
                if (edge.toVertex != parent) {
                    min = Math.min(min, test(T, L, edge.toVertex, vertex, length + edge.weight, edges + 1));
                }
            }
            return min;
        } else {
            return (int) 1e9; // overshoot
        }
    }
}
// provided code
public class Main {
    static void putEdge(List<List<Edge>> T, int vertex1, int vertex2, int weight) {
        T.get(vertex1).add(new Edge(vertex2, weight));
        T.get(vertex2).add(new Edge(vertex1, weight));
    }
    public static void main(String[] args) {
        // example input
        List<List<Edge>> T = new ArrayList<List<Edge>>();
        int N = 8;
        for (int i = 0; i < N; i++) T.add(new ArrayList<Edge>());
        putEdge(T, 0, 1, 2);
        putEdge(T, 1, 2, 1);
        putEdge(T, 1, 3, 2);
        putEdge(T, 2, 6, 1);
        putEdge(T, 6, 7, 1);
        putEdge(T, 3, 4, 1);
        putEdge(T, 3, 5, 4);
        System.out.println(Solver.solve(T, 5L)); // path from 4 to 5 have 2 edges and length 5
    }
}

Но это превышает лимит времени, когда N достигает около 10 000. Я также рассматривал бинарный поиск ответа, но проверка возможности конкретного ответа выглядит так же сложно, как и решение исходной задачи.

Есть ли более эффективный способ решить эту проблему, чтобы каким-то образом избежать проверки всех путей?

Вы имеете в виду вес L? Кратчайший путь L должен быть L длинным, не так ли? И еще, нужно ли точно приземлиться на вес L или хотя бы L? (Второе гораздо проще.)

btilly 01.07.2024 23:05

@btilly Я написал длину как сумму весов, поэтому отредактировал ее. Это точно Л.

user25680598 02.07.2024 01:05

Вам нужно только вычислить суммы и длины восходящих путей. Путь, который вы будете искать, на некоторое расстояние поднимается в одну сторону, а затем падает в другую, и его можно сопоставить с помощью поиска по хешу. Если глубина дерева d, это будет O(d n). В случайном дереве d = O(log(n)), так что это улучшение.

btilly 02.07.2024 01:57

Можете ли вы также добавить образец тестового примера?

Rohan Sharma 02.07.2024 22:14

@RohanSharma В основном методе есть пример ввода (прокрутите код вниз).

user25680598 03.07.2024 04:14

@btilly это интересно. не уверен, что тестовые случаи являются случайными, хотя

user25680598 03.07.2024 04:16

Что это за дерево? Это N-арное или двоичное дерево?

Rohan Sharma 03.07.2024 21:00

@RohanSharma не двоичный. из каждой вершины может выходить любое количество ребер. никаких ограничений на это

user25680598 03.07.2024 21:59
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
8
270
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Рассмотрим следующий алгоритм:

# Walk the tree in every node from Root node *Recursively (parent-weight=0):
  1 Sort the edges by Weight Descending
  2 Iterate the sorted edges in the node.
    2.1 Calculate the branch-weight: node parent-weight + edge weight
    2.2 IF branch-weight = L THEN Bingo! :)
        ELSE if branch-weight > L THEN Skip edge
        ELSE Walk the egde's end node *Recursively (parent-weight = branch-weight).

Я считаю, что это вполне оптимальный алгоритм.

Я не понимаю, как это позволит найти пути, которые не включают корень или которые включают корень, но идут вниз по двум ветвям.

Luatic 06.07.2024 00:41

Ах, хорошо, я использовал другое распространенное определение «пути» как путь от корня...

MGM 08.07.2024 14:33

путь — это путь в любом месте дерева. также у дерева нет корня, это просто N вершин и N-1 ребер.

user25680598 09.07.2024 21:06

Да, по математике.

MGM 22.07.2024 18:20
Ответ принят как подходящий

Есть два основных способа решения этой проблемы; Я опишу более простой метод.

Для начала произвольным образом укорените дерево (вершина 0, как правило, является хорошим выбором, поскольку она всегда существует). Обозначим через dist[x] сумму весов ребер на пути от корня до x, а через depth[x] обозначим количество ребер на этом пути. Для любых двух различных узлов u и v между ними существует один уникальный простой путь, который идет от u к наименьшему общему предку (LCA) двух узлов, а затем к v. Мы можем выразить общий вес на этом пути как dist[u] + dist[v] - 2 * dist[LCA(u, v)], поскольку ребра от корня до LCA учитываются как в dist[u], так и в dist[v]. Аналогично, количество ребер на пути равно depth[u] + depth[v] - 2 * depth[LCA(u, v)].

Далее давайте рассмотрим каждый узел n как возможный LCA на пути между двумя узлами веса L. В этом случае оба этих узла должны находиться в поддереве n (включая его самого). Чтобы вычислить оптимальный ответ в каждом узле, мы будем хранить карту для каждого узла, которая сопоставляет каждому возможному dist[x] минимальное depth[x], которое может достичь этого расстояния для любого x в поддереве n.

Чтобы обработать узел n, мы перебираем каждый дочерний узел c и сначала вычисляем для него эту карту, а затем объединяем этот результат в карту текущего узла в два этапа. Для каждой пары ключ-значение (d, e) в дочернем поддереве мы проверяем карту текущего узла n на наличие ключа k, который удовлетворяет k + d - 2 * dist[n] = L. Если он существует, мы нашли путь веса L. Теперь мы можем обновить наш ответ минимумом текущего ответа и суммой количества ребер для двух частей этого пути. После выполнения всех необходимых обновлений ответа с поддеревом c мы обновляем карту для узла n картой для c, сохраняя минимальное количество ребер для всех замеченных на данный момент расстояний (чтобы убедиться, что мы можем найти оптимальные пути из карты при рассмотрении последующих поддеревьев).

Чтобы эффективно обновлять эти карты, мы будем выбирать всегда обновлять карту с большим количеством ключей, используя карту с меньшим количеством ключей. В худшем случае все узлы имеют разные расстояния от корня. Всякий раз, когда расстояние до определенного узла добавляется от одной карты к другой, результирующая карта должна быть как минимум в два раза больше размера старой карты. Размер любой карты не может превышать N ключей, поэтому каждый элемент можно добавить не более чем на log N карты. Каждый узел вносит свой вклад в обновления log N, поэтому временная и пространственная сложность O(N log N) являются одновременно dist[x].

public static int solve(List<List<Edge>> T, long L) {
    var minEdgesForDist = Stream.<Map<Long, Integer>>generate(HashMap::new).limit(T.size()).collect(Collectors.toCollection(ArrayList::new));
    return new Object() { 
        // creating new object to define methods inside the context of the solve method
        int dfs(int node, int par, int depth, long dist) {
            minEdgesForDist.get(node).put(dist, depth); // for node itself
            int ret = Integer.MAX_VALUE;
            for (var edge : T.get(node))
                if (edge.toVertex != par) {
                    ret = Math.min(ret, dfs(edge.toVertex, node, depth + 1, dist + edge.weight));
                    if (minEdgesForDist.get(edge.toVertex).size() > minEdgesForDist.get(node).size())
                        Collections.swap(minEdgesForDist, edge.toVertex, node); // important!
                    for (var entry : minEdgesForDist.get(edge.toVertex).entrySet()) {
                        var other = minEdgesForDist.get(node).get(L + 2 * dist - entry.getKey());
                        if (other != null)
                            ret = Math.min(ret, entry.getValue() + other - 2 * depth);
                    }
                    for (var entry : minEdgesForDist.get(edge.toVertex).entrySet())
                        minEdgesForDist.get(node).merge(entry.getKey(), entry.getValue(), Math::min);
                }
            return ret;
        }
        
        int getAnswer() {
            int ret = dfs(0, 0, 0, 0);
            return ret != Integer.MAX_VALUE ? ret : -1;
        }
    }.getAnswer();
}

очень крутое решение!!

user25680598 09.07.2024 21:06

Другие вопросы по теме