Самая длинная «подпоследовательность» сбалансированных скобок

Я пытаюсь решить вариант проблема, которую я задавал ранее:

Имея строку скобок (длиной ‹= 1 000 000) и список запросов диапазона, найдите самую длинную подпоследовательность сбалансированных скобок в каждом из диапазонов для каждого из ‹= 100 000 запросов.

Я нашел этот другой вопрос SO, который похож, но имеет только O (N^3) алгоритм.

Я считаю, что решение DP в форме dp[i, j] = longest balanced subsequence in [i .. j] должно работать, потому что после вычисления это позволит ответить на все запросы диапазона, просто запросив таблицу DP. Однако даже решение этой проблемы за O(N^2) превысило бы ограничения по времени из-за большой возможной длины входной строки.

Кроме того, трюк с использованием стека для отслеживания соответствия скобок больше не работает напрямую, потому что вы ищете подпоследовательности, а не подстроки.

У меня есть метод, который, я думаю, может работать, но не уверен:

Длина самой длинной подпоследовательности сбалансированных скобок в интервале равна сумме длин самых длинных непересекающихся подстрок сбалансированных скобок в этом интервале.

Например, если у вас есть строка

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

) ) ( ( ) ( ( ) ) ) ) ( ( ) )

Длина самой длинной подпоследовательности сбалансированных скобок в интервале [0, 8] (включительно) равна 5. Эта длина равна сумме длин самых длинных непересекающихся подстрок в интервале: "()" + "( ( ) )".

Будет ли этот метод всегда работать, или есть лучший способ?


person 1110101001    schedule 30.10.2014    source источник
comment
существует ли эта проблема в какой-то системе онлайн-судей?   -  person Herokiller    schedule 30.10.2014
comment
Будет ли этот метод работать всегда или есть лучший способ? Да, и да. Существует много возможных максимальных подпоследовательностей, и почти любой разумный способ их создания будет работать. На самом деле есть простой O(n) ответ. Но я не разрушу любой конкурс, от которого вы его получили, отдав его вам.   -  person btilly    schedule 30.10.2014
comment
@Herokiller Эта проблема представляет собой вариант, который я придумал, основываясь на этой прошлой проблеме конкурса: usaco .org/index.php?page=viewproblem2&cpid=194   -  person 1110101001    schedule 30.10.2014
comment
@user2612743 user2612743 Я думаю, что мы можем сделать здесь динамику O (n), но не можем проверить без реальных тестов.   -  person Herokiller    schedule 30.10.2014
comment
@btilly Включает ли решение O (n) использование стека, а затем, при обнаружении совпадающей пары, извлечение из стека?   -  person 1110101001    schedule 30.10.2014
comment
@btilly Кроме того, если O (n) для каждого запроса с длиной диапазона n, то в худшем случае каждый из 100000 запросов предназначен для полного диапазона длины 1000000, который недостаточно оптимизирован. Должен быть способ, чтобы каждый запрос был log(N) или лучше...   -  person 1110101001    schedule 30.10.2014
comment
codeforces.com/contest/5/problem/C   -  person Rezwan Arefin    schedule 27.02.2017


Ответы (2)


Поскольку кто-то другой опубликовал ответ, вот O(n) ответ на один запрос с O(1) пробелом. Сохраняйте сбалансированное количество скобок и указатели на последнюю открытую и последнюю закрытую скобку. Пока вы не закончите строку, сканируйте вперед последнюю открытую строку, чтобы найти другую открытую скобку. Затем просканируйте вперед от максимума последней открытой и последней закрытой скобки, чтобы найти следующую закрытую скобку. Если вы найдете пару таким образом, увеличьте количество сбалансированных скобок. Когда вы дойдете до конца строки, у вас будет правильный счет, даже если вы неправильно соединили парные скобки.

На самом деле может быть несколько максимальных подпоследовательностей сбалансированных скобок. Но если вы возьмете любую максимальную подпоследовательность сбалансированных скобок и замените каждую открытую скобку самой левой открывающей скобкой, а затем каждую закрывающую скобку самой левой из возможных открытых скобок, результатом будут те, которые вы нашли. (Доказательство оставлено читателю в качестве поучительного упражнения.)

Вот псевдокод.

parens = 0
last_open = 0
last_closed = 0
while last_open < len(str) && last_closed < len(str):
    if str[last_open] == ')':
        # We are looking for the next open paren.
       last_open += 1
    elif last_closed < last_open:
       # Start our search for a last closed after the current char
       last_closed = last_open + 1
    elif str[last_closed] == '(':
       # still looking for a close pair
       last_closed += 1
    else:
       # We found a matching pair.
       parens += 1
       last_open += 1
# and now parens has the correct answer.

И затем у нас есть проблема со многими запросами диапазона. Оказывается, чтобы сделать это быстро, требуется O(n) предварительных вычислений и O(n) места, а каждый запрос диапазона будет занимать O(log(n)) времени.

Вот подсказка для этой проблемы. Предположим, что у нас есть 2 блока A и B рядом друг с другом. Каждый из которых внутри имеет некоторое количество сбалансированных подпоследовательностей скобок, некоторое количество дополнительных открытых скобок, доступных справа, и некоторое количество дополнительных закрывающих скобок, доступных слева. Тогда объединенный блок C имеет следующее:

C.balanced = A.balanced + B.balanced + min(A.open, B.close)
C.open = B.open + max(A.open - B.close, 0)
C.close = A.close + max(B.close - A.open, 0)

Я оставляю вам задачу выяснить, какой набор блоков нужно предварительно вычислить, чтобы иметь возможность вычислить любой блок за время O(log(n)).

person btilly    schedule 30.10.2014
comment
Если вы хотите эффективно повторить это для запросов с несколькими диапазонами, не могли бы вы каким-то образом включить в это суммы префиксов методов? Или же создать хэш-карту, которая сопоставляет last_open и last_closed со значением скобок, что позволяет выполнять запросы диапазона? - person 1110101001; 30.10.2014
comment
@user2612743 user2612743 Я добавил подсказку, как настроить этот метод для многих эффективных запросов диапазона. - person btilly; 30.10.2014
comment
Вы должны выполнять предварительные вычисления в блоках размера два, верно? Таким образом, учитывая любой диапазон, вы можете продолжать делить пополам, пока не получите блок размера два, и комбинировать различные блоки размера два, чтобы получить блок размера 4, 8 и так далее, пока не получите полный диапазон. - person 1110101001; 31.10.2014
comment
Если вы запустите алгоритм на строке ((), вы получите ответ 2 для количества наборов совпадающих скобок, когда он должен дать только 1 - person 1110101001; 31.10.2014
comment
@ user2612743 Как так? Независимо от того, рассматриваю ли я это как ((+) или (+(), (() дает мне открытие 1, балансировку 1, закрытие 0. Так и должно быть. - person btilly; 02.11.2014
comment
Я придумал ту же идею для одного запроса, и она работает для нескольких примеров. Но у меня нет доказательств. Любая интуиция о том, почему это находит ответ? - person sinoTrinity; 07.05.2015
comment
@sinoTrinity Интуиция такова, что это находит максимальный набор совпадающих скобок. И набор совпадающих скобок всегда является набором сбалансированных скобок, с той лишь разницей, что скобки соответствуют друг другу. - person btilly; 07.05.2015

Я опишу решение O (n)

Во-первых, у нас есть массив dp[n] для каждой позиции i , если i является закрывающей скобкой ), dp[i] будет хранить самую дальнюю позицию, которая образует допустимую последовательность скобок, заканчивающуюся на i.

Мы поддерживаем стек, в котором отслеживаются открытые скобки и их положение. Поэтому, если мы встречаем открытую скобку, мы помещаем ее в стек вместе с ее местоположением, а если мы встречаем закрывающую скобку, мы извлекаем последнюю открытую скобку и обновляем массив dp.

  • dp[i] = min (position of open bracket, dp[position of open bracket - 1] ), это проверит, есть ли перед открывающей скобкой закрывающая скобка, если да, то улучшаем dp[i]

Таким образом, ответом будет наибольшее значение между i - dp[i]

Java-код:

    public static void main(String[] args) {
    String val = "))(()(())))(())";// Which store the parentheses sequence
    int[] dp = new int[val.length()];
    Arrays.fill(dp, -1);

    Stack<Integer> stack = new Stack();
    for (int i = 0; i < val.length(); i++) {
        char c = val.charAt(i);
        if (c == '(')
            stack.push(i);
        else if (!stack.isEmpty()) {
            int v = stack.pop();
            dp[i] = v;
            if (v > 0 && val.charAt(v - 1) == ')')
                if (dp[v - 1] != -1)
                    dp[i] = dp[v - 1];
        }
    }
    int result = 0;
    for (int i = 0; i < val.length(); i++){
        if (dp[i] != -1){
            System.out.println(val.substring(dp[i] , i + 1));

            result = Math.max(result, i - dp[i] + 1);
        }
    }
    System.out.println(result);
}

Вывод:

()
()
()(())
(()(()))
()
(())
8
person Pham Trung    schedule 30.10.2014
comment
Однако это O (n) для каждого запроса диапазона. Если у вас есть N запросов диапазона, каждый из которых имеет длину интервала N, это становится O (N ^ 2), что слишком велико. Есть ли способ получить O (lg N) или меньше для каждого запроса, чтобы получить общее время выполнения O (N lgN) или лучше, когда у вас есть несколько запросов? - person 1110101001; 30.10.2014
comment
@user2612743 user2612743 хм, я думаю, что в вашей ссылке есть один человек, который ранее отвечал на ваш вопрос, поэтому, исходя из результата моего алгоритма, вам нужно построить древовидную структуру допустимых скобок и запросить это дерево, что может улучшить производительность. ! - person Pham Trung; 30.10.2014
comment
Разве приведенный выше алгоритм не обеспечивает только самую длинную подстроку сбалансированных скобок, а не подпоследовательность? - person 1110101001; 30.10.2014
comment
@user2612743 user2612743 хм, вы правы, это не подпоследовательность, мне нужно будет изменить мой ответ - person Pham Trung; 30.10.2014
comment
Стек, вероятно, неприменим при работе с подпоследовательностью, а не с подстрокой. - person sinoTrinity; 07.05.2015