Quante operazioni di inversione di tutte le parentesi su una sottostringa di una stringa di parentesi sono necessarie per rendere la stringa "corretta"?

cs.stackexchange https://cs.stackexchange.com/questions/119847

  •  28-09-2020
  •  | 
  •  

Domanda

Chiamo una stringa di parentesi "corretta" se ogni parentesi di apertura ha una parentesi di chiusura corrispondente da qualche parte dopo di essa e ogni parentesi di chiusura ha una parentesi di apertura corrispondente da qualche parte prima di essa.Per esempio

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

è una stringa corretta e

)()()(

è una stringa errata.

Un'operazione consiste nel prendere una sottosequenza contigua della stringa di input e 'capovolgere' ogni parentesi in quella sottosequenza.

Data una stringa arbitraria di parentesi (di lunghezza pari), il compito è trovare il minor numero di operazioni di questo tipo necessarie per trasformarla in una stringa di parentesi "corretta".

Un altro modo di affrontare questo problema è avere una stringa di +1 e -1 e cercare di trovare il minor numero di operazioni (di moltiplicare ogni numero su un intervallo per -1) necessarie per rendere ogni prefisso di questa stringa non negativo e ogni suffisso non positivo.

È stato utile?

Soluzione

Il DP ha suggerito nei commenti di Yuval Filmus funziona davvero: possiamo risolvere il problema in $ \ mathcal {o} (n ^ {2}) $ DP.

Prima nota che potremmo supporre che gli intervalli non siano autorizzati a sovrapporsi. Prendi due intervalli di sovrapposizione $ [a, b) $ e $ [c, d) $ . Wlog $ a \ leq c $ . Quindi possiamo sostituire i due intervalli con gli intervalli $ [A, c) $ e $ [\ min (B, D ), \ max (B, D)) $ che non si sovrappongono, hanno lunghezza totale più breve e coprire esattamente gli stessi elementi.

Sostituire le staffe di apertura con $ 1 $ , staffe di chiusura con $ - 1 $ . Ora il problema corrisponde all'applicazione del numero minimo di operazioni "Intervallo multiplica di $ - 1 $ " Tale che ogni prefisso ha una somma non negativa e la somma sull'intero array è zero.

Costruiamo una soluzione da sinistra a destra. In ogni posizione possiamo iniziare un intervallo, o terminare un intervallo attivo.

Let $ DP [m] [s] [0] $ Dennare il numero minimo di intervalli che dobbiamo usare tale che ogni somma di prefisso nel primo $ m $ Elementi non è negativo, la somma della prima $ m $ elementi è $ S $ e non abbiamo un intervallo attivo. Allo stesso modo $ DP [m] [s] [1] $ è lo stesso valore in cui abbiamo un intervallo attivo.

Se la $ m $ il valore è $ 1 $ , per $ 0 \ Leq S \ Leq m $ Impostato

    .
  • $ DP [m] [s] [0]= DP [M-1] [S-1] [0] $
  • $ DP [m] [s] [1]= DP [M-1] [S + 1] [1] $

Altrimenti impostiamo

    .
  • $ DP [m] [s] [0]= DP [M-1] [s + 1] [0] $
  • $ DP [m] [s] [1]= DP [M-1] [S-1] [1] $ .

Quindi finiamo e avviando intervalli, impostazione

    .
  • $ DP [m] [s] [0]=min (DP [m] [s] [0], DP [m] [s] [1]) $
  • $ DP [m] [s] [1]=min (DP [m] [s] [1], dp [m] [s] [0] + 1) $

Il caso base è $ DP [0] [0] [0]= 0 $ , $ DP [0 ] [0] [1]= 1 $ .

Ci sono $ \ mathcal {o} (n ^ {2}) $ afferma, e facciamo $ \ Mathcal {o} (1) $ Lavora per calcolare ciascuno di essi, quindi l'algoritmo funziona in $ \ mathcal {o} (n ^ {2}) $ . Il DP può essere implementato per utilizzare $ \ mathcal {o} (n) $ memoria memorizzando solo i valori di $ DP $ per la corrente $ m $ .

Ecco un'implementazione C ++ dell'algoritmo.

#include <iostream>
#include <vector>
using namespace std;

int main() {
    string str;
    cin >> str;
    int n = str.size();

    vector<int> dp0 = {0}, dp1 = {1};
    for (int m = 1; m <= n; ++m) {
        vector<int> nxt0(m + 1, n + 1), nxt1(m + 1, n + 1);
        for (int i = 0; i < m; ++i) {
            if (str[m-1] == '(') {
                nxt0[i+1] = dp0[i];
                if (i > 0) nxt1[i-1] = dp1[i];
            } else {
                nxt1[i+1] = dp1[i]);
                if (i > 0) nxt0[i-1] = dp0[i];
            }
        }
        dp0 = nxt0;
        dp1 = nxt1;
        for (int i = 0; i <= m; ++i) dp1[i] = min(dp1[i], dp0[i] + 1); // Start interval
        for (int i = 0; i <= m; ++i) dp0[i] = min(dp0[i], dp1[i]); // Stop interval
    }
    cout << dp0[0] << '\n';
}
.

Altri suggerimenti

Bella domanda!

Ecco il problema in termini più popolari.

Parentesi bilanciate

Una stringa di parentesi è bilanciata se possiamo cambiarla in una stringa vuota rimuovendo ripetutamente le sottostringhe "()".Ad esempio, la stringa vuota "()" e "(())()((()()))" sono bilanciate ma nessuno di "())" e ")()()(" è bilanciato.

È chiaro che una stringa bilanciata deve iniziare con "(" e terminare con ")".Deve essere anche di lunghezza uniforme.

il problema delle operazioni minime per bilanciare una stringa

Permettere s essere una stringa non vuota di parentesi di lunghezza n.L'unica operazione che possiamo eseguire è invertire ogni parentesi in una sottostringa, ovvero cambiare ogni "(" in ")" e ogni ")" in "(" per alcuni caratteri contigui nella stringa.Il problema è come trovare il minor numero di operazioni possibili s equilibrato.

un approccio di programmazione dinamica

Quali sono i sottoproblemi appropriati?Sono dp[i][j] per 0 <= i < j < n, Dove dp[i][j] è il numero minimo di operazioni che bilanciano la sottostringa tra indice i e indice j compreso.Il numero minimo desiderato di operazioni che si bilanciano s È dp[0][n-1].

Poiché qualsiasi stringa di lunghezza dispari non può essere bilanciata, d'ora in poi limiteremo la nostra attenzione alle stringhe di lunghezza pari.

i casi base dp[i][i+1] è 0 se la sottostringa tra i E i+1 è bilanciato, cioè "()".Altrimenti è 1.

la relazione di ricorrenzaUtilizzerò Java per definire la relazione di ricorrenza.

void dp(int i, int j) {
    assert 0 <=i && i + 2 < j && j <= L;

    int min = dp[i + 1][j - 1];
    if (s.charAt(i) == ')' && s.charAt(i + 1) == '(') min++;
    if (s.charAt(j) == '(' && s.charAt(j - 1) == ')') min++;

    for (int k = i + 1; k < j - 1; k+=2) {
        if (a[k] == 1 && a[k + 1] == -1) {
            if (min > dp[i][k] + dp[k + 1][j] - 1) {
                min = dp[i][k] + dp[k + 1][j] - 1;
            }
        } else {
            if (min > dp[i][k] + dp[k + 1][j]) {
                min = dp[i][k] + dp[k + 1][j];
            }
        }
    }

    dp[i][j] = min;
}

La relazione di ricorrenza deriva dalla seguente proprietà ben nota delle stringhe bilanciate.Una stringa non vuota è bilanciata se e solo se è la concatenazione di due stringhe bilanciate oppure "(" seguito da una stringa bilanciata seguita da ")".

Il trattamento prima del for ciclo, che imposta min essere dp[i + 1][j - 1] più 0, 1 o 2, deriva dal fatto che "(" seguito da una stringa bilanciata seguita da ")" deve essere una stringa bilanciata.

L'elaborazione in for loop, che tenta di restringersi min A dp[i][k] + dp[k + 1][j] meno 0 o 1 per alcuni k quello è tra i E j, deriva dal fatto che la concatenazione di due stringhe bilanciate deve essere una stringa bilanciata.

La complessità temporale di questo approccio è $O(n^3)$.La complessità spaziale è $O(n^2)$.

Lunghezza delle corde "più sbilanciate".

La lunghezza più piccola di una stringa che non può essere bilanciata con meno di 2 operazioni è 4.Per esempio, ")((("

La lunghezza più piccola di una stringa che non può essere bilanciata con meno di 3 operazioni è 10.Per esempio, ")(((((())("

La lunghezza minima di una stringa che non può essere bilanciata con meno di 4 operazioni è 22.Per esempio, ")(())))(((((((((((())(".

Domanda:Qual è la lunghezza più piccola di una stringa che richiede almeno 5 operazioni?Come ho calcolato, deve essere maggiore di 28.Tuttavia, i miei calcoli non sono sufficienti per determinare il valore effettivo.Quanto velocemente possiamo calcolare queste lunghezze più piccole?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a cs.stackexchange
scroll top