Credo che il tempo logaritmico per tutte le domande sia realizzabile. L'idea principale è quella di utilizzare un albero degli intervalli, in cui ciascun nodo dell'albero corrisponde a un intervallo di indici. Svilupperò le idee chiave iniziando con una versione più semplice della struttura dei dati (che può supportare get e set ma non le altre operazioni), quindi aggiungerò funzionalità per supportare anche le altre funzionalità.
Uno schema semplice (supporta get e set, ma non aggiungere o pugnalare)
Supponiamo che un intervallo sia piatto se la funzione è costante su , cioè se .f [ a , b ] f ( a ) = f ( a + 1 ) = ⋯ = f ( b )[a,b]f[a,b]f(a)=f(a+1)=⋯=f(b)
La nostra semplice struttura di dati sarà una struttura ad intervalli. In altre parole, abbiamo un albero binario, in cui ogni nodo corrisponde a un intervallo (di indici). Memorizzeremo l'intervallo in ciascun nodo dell'albero. Ogni foglia corrisponderà ad un intervallo piatto e saranno disposti in modo tale che la lettura delle foglie da sinistra a destra ci dia una sequenza di intervalli piatti consecutivi che sono disgiunti e la cui unione è tutta di . L'intervallo per un nodo interno sarà l'unione degli intervalli dei suoi due figli. Inoltre, in ogni nodo foglia memorizzeremo il valore della funzione sull'intervallov [ 1 , n ] ℓ V ( ℓ ) f I ( ℓ ) f fI(v)v[1,n]ℓV(ℓ)fI(ℓ)corrispondente a questo nodo (si noti che questo intervallo è piatto, quindi è costante sull'intervallo, quindi memorizziamo un singolo valore di in ciascun nodo foglia).ff
Allo stesso modo, puoi immaginare che partizioniamo in intervalli piatti, e quindi la struttura dei dati è un albero di ricerca binario in cui le chiavi sono gli endpoint di sinistra di quegli intervalli. Le foglie contengono il valore di in un intervallo di indici in cui è costante.f f[1,n]ff
Utilizzare metodi standard per garantire che l'albero binario rimanga bilanciato, ovvero che la sua profondità sia (dove conta il numero corrente di foglie nell'albero). Certo, , quindi la profondità è sempre al massimo . Questo sarà utile di seguito.m m ≤ n O ( lg n )O(lgm)mm≤nO(lgn)
Ora possiamo supportare le operazioni get e set come segue:
i O ( lg n ) O ( lg n )get(i) è facile: attraversiamo l'albero per trovare la foglia il cui intervallo contiene . Fondamentalmente si tratta solo di attraversare un albero di ricerca binario. Poiché la profondità è , il tempo di esecuzione è .iO(lgn)O(lgn)
set([a,b],y) è più complicato. Funziona così:
Innanzitutto, troviamo l'intervallo foglia contenente ; se , allora suddividiamo questo intervallo foglia in due intervalli e (trasformando così questo nodo foglia in un nodo interno e introducendo due figli).a a 0 < a [ a 0 , a - 1 ] [ a , b 0 ][a0,b0]aa0<a[a0,a−1][a,b0]
Successivamente, troviamo l'intervallo foglia contenente ; se , suddividiamo questo intervallo foglia nei due intervalli e (trasformando così questo nodo foglia in un nodo interno e introducendo due figli).b b < b 1 [ a 1 , b ] [ b + 1 , b 1 ][a1,b1]bb<b1[a1,b][b+1,b1]
A questo punto, sostengo che l'intervallo può essere espresso come l'unione disgiunta di intervalli corrispondenti ad alcuni sottogruppi di nodi nella struttura ad albero. Quindi, elimina tutti i discendenti di quei nodi (trasformandoli in foglie) e imposta il valore memorizzato in quei nodi su .O ( lg n ) O ( lg n ) y[a,b]O(lgn)O(lgn)y
Infine, poiché abbiamo modificato la forma dell'albero, eseguiremo tutte le rotazioni necessarie per riequilibrare l'albero (utilizzando qualsiasi tecnica standard per mantenere un albero in equilibrio).
Poiché questa operazione comporta alcune semplici operazioni sui nodi (e quella serie di nodi può essere facilmente trovata nel tempo ), il tempo totale per questa operazione è .O ( lg n ) O ( lg n )O(lgn)O(lgn)O(lgn)
Questo dimostra che possiamo supportare sia le operazioni get che set in per operazione. In effetti, il tempo di esecuzione può essere indicato come , dove è il numero di operazioni impostate eseguite fino ad ora.O ( lg min ( n , s ) ) sO(lgn)O(lgmin(n,s))s
Aggiunta del supporto per Aggiungi
Siamo in grado di modificare la struttura di dati di cui sopra in modo che possa supportare anche l'operazione di aggiunta. In particolare, invece di memorizzare il valore della funzione nelle foglie, verrà rappresentata come la somma dei numeri memorizzati in un insieme di nodi.
Più precisamente, il valore della funzione all'ingresso sarà recuperabile come la somma dei valori memorizzati nei nodi sul percorso dalla radice dell'albero fino alla foglia il cui intervallo contiene . In ogni nodo memorizzeremo un valore ; se rappresentano gli antenati di una foglia (compresa la foglia stessa), il valore della funzione su sarà .i i v V ( vf(i)iivv 0 , v 1 , … , v k v k I ( v k ) V ( v 0 ) + ⋯ + V ( v k )V(v)v0,v1,…,vkvkI(vk)V(v0)+⋯+V(vk)
È facile supportare le operazioni get e set usando una variante delle tecniche sopra descritte. Fondamentalmente, mentre attraversiamo l'albero verso il basso, teniamo traccia della somma corrente dei valori, in modo che per ogni nodo che la visita attraversi, conosciamo la somma dei valori dei nodi sul percorso dalla radice a . Una volta fatto ciò, saranno sufficienti semplici adattamenti all'implementazione di get e set sopra descritti.xxx
E ora possiamo supportare efficiente. Innanzitutto, esprimiamo l'intervallo come l'unione di intervalli corrispondenti a un insieme di nodi nella struttura (suddividendo un nodo all'endpoint sinistro e all'endpoint destro se necessario ), esattamente come fatto nei passaggi 1-3 dell'operazione impostata. Ora, aggiungiamo semplicemente al valore memorizzato in ciascuno di quei nodi . (Non eliminiamo i loro discendenti.)[ a , b ] O ( lg n ) O ( lg n ) δ O ( lg n )add([a,b],δ)[a,b]O(lgn)O(lgn)δO(lgn)
Questo fornisce un modo per supportare ottenere, impostare e aggiungere, in tempo per operazione. In effetti, il tempo di esecuzione per operazione è dove conta il numero di operazioni impostate più il numero di operazioni di aggiunta.O ( lg min ( n , s ) ) sO(lgn)O(lgmin(n,s))s
Supportare l'operazione di pugnalata
La query lancinante è la più impegnativa da supportare. L'idea di base sarà quella di modificare la struttura di dati sopra per preservare il seguente invariante aggiuntivo:
(*) L'intervallo corrispondente a ciascuna foglia è un intervallo massimo piatto.ℓI(ℓ)ℓ
Qui dico che un intervallo è un intervallo massimo piatto se (i) è piatto, e (ii) nessun intervallo contenente è piatto (in altre parole, per tutto soddisfacente , o non è piatto).[ a , b ] [ a , b ] a ′ , b ′ 1[a,b][a,b][a,b]a′,b′[ a ′ , b ′ ] = [ a , b ] [ a ′ , b ′ ]1≤a′≤a≤b≤b′≤n[a′,b′]=[a,b][a′,b′]
Ciò rende l'operazione di stabilizzazione facile da implementare:
- istab(i) trova la foglia il cui intervallo contiene , quindi restituisce quell'intervallo.i
Tuttavia, ora dobbiamo modificare il set e aggiungere operazioni per mantenere l'invariante (*). Ogni volta che dividiamo una foglia in due, potremmo violare l'invariante se alcune coppie adiacenti di intervalli di foglie hanno lo stesso valore della funzione . Fortunatamente, ogni operazione di impostazione / aggiunta aggiunge al massimo 4 nuovi intervalli di foglie. Inoltre, per ogni nuovo intervallo, è facile trovare l'intervallo foglia immediatamente a sinistra e a destra di esso. Pertanto, possiamo dire se l'invariante è stato violato; in tal caso, uniamo gli intervalli adiacenti in cui ha lo stesso valore. Fortunatamente, l'unione di due intervalli adiacenti non provoca cambiamenti in cascata (quindi non è necessario verificare se la fusione potrebbe aver introdotto ulteriori violazioni dell'invariante). Complessivamente, ciò implica l'esame dif 12 = O ( 1 ) O ( lg n )ff12=O(1)coppie di intervalli e possibilmente fondendole. Infine, poiché una fusione modifica la forma dell'albero, se ciò viola gli invarianti di equilibrio, esegui tutte le rotazioni necessarie per mantenere l'albero equilibrato (seguendo le tecniche standard per mantenere equilibrati gli alberi binari). In totale, questo aggiunge al massimo lavoro aggiuntivo alle operazioni set / add.O(lgn)
Pertanto, questa struttura dati finale supporta tutte e quattro le operazioni e il tempo di esecuzione per ciascuna operazione è . Una stima più precisa è il tempo per operazione, dove conta il numero di operazioni impostate e aggiunte.O ( lg min ( n , s ) ) sO(lgn)O(lgmin(n,s))s
Pensieri di separazione
Accidenti, questo era uno schema piuttosto complesso. Spero di non aver fatto errori. Si prega di controllare attentamente il mio lavoro prima di fare affidamento su questa soluzione.
add
sarebbe lineare nel numero di sottointervalli di ; hai pensato a un albero splay con ulteriori nodi unari " ", compattati pigramente? + δ