Skip to Content

Suitable Edit for LIS

Análisis oficial 

Explicación

Podemos resolver este problema con la técnica de LIS con Árbol de Segmentos.

Si la solución óptima contiene una actualización en el índice ii, actualizaremos AiA_i a Ai1+1A_{i-1}+1 si i>1i>1 y a 00 en caso contrario. Si tenemos alguna subsecuencia creciente que contiene el valor Ai1A_{i-1} y queremos agregar un nuevo elemento justo después de Ai1A_{i-1}, es óptimo elegir Ai1+1A_{i-1}+1. Esto se debe a que maximiza la cantidad de opciones para el siguiente elemento mientras sigue siendo >Ai1>A_{i-1}. Siempre es óptimo actualizar AiA_i a Ai1+1A_{i-1}+1 porque si Ai1+1A_{i-1}+1 sigue directamente a Ai1A_{i-1}, no debemos tomar ningún índice entre el índice de Ai1A_{i-1} y Ai1+1A_{i-1}+1. Actualizar el valor en el índice ii a Ai1+1A_{i-1}+1 hace que el AiA_i original sea el único valor que no podemos tomar. Si actualizamos el valor en un índice posterior, no podremos tomar un conjunto más grande de valores que aún incluya AiA_i.

Para i=1i=1, es óptimo poner Ai=0A_i=0 porque cualquier valor mayor de AiA_i hará que el conjunto de posibles valores siguientes en la LIS sea un subconjunto del conjunto de posibles valores siguientes en la LIS cuando Ai=0A_i=0.

Podemos usar 22 Árboles de Segmentos. Denotémoslos como before\texttt{before} y after\texttt{after}. beforei\texttt{before}_i guardará la LIS que termina con el valor ii sin ninguna operación realizada. afteri\texttt{after}_i guardará la LIS que termina con el valor ii después de realizar una operación. También denotemos besti\texttt{best}_i como el mejor reemplazo para AiA_i.

Podemos transicionar de la siguiente forma:

afterAi=max(afterAi, 1+maxj<Aiafterj) \texttt{after}_{A_i}=\max(\texttt{after}_{A_i},\ 1+\max_{j<A_i}{\texttt{after}_j}) afterbesti=max(afterbesti, 1+maxj<bestibeforej) \texttt{after}_{\texttt{best}_i}=\max(\texttt{after}_{\texttt{best}_i},\ 1+\max_{j<\texttt{best}_i}{\texttt{before}_j}) beforeAi=max(beforeAi, 1+maxj<Aibeforej) \texttt{before}_{A_i}=\max(\texttt{before}_{A_i},\ 1+\max_{j<A_i}{\texttt{before}_j})

La primera y la tercera transición extienden la LIS. La segunda transición se usa para aplicar la operación. Nuestro resultado final es el valor máximo en after\texttt{after}.

Notamos que, como Ai109A_i \leq 10^9, necesitaremos usar compresión de coordenadas.

Implementación

Complejidad temporal: O(NlogN)\mathcal{O}(N\log N)

#include <bits/stdc++.h> using namespace std; // BeginCodeSnip{Range Maximum Segment Tree} class MaxSegTree { private: int len; vector<int> segtree; public: MaxSegTree(int len) : len(len), segtree(2 * len) {} void set(int ind, int val) { ind += len; for (segtree[ind] = max(segtree[ind], val); ind > 1; ind >>= 1) { segtree[ind >> 1] = max(segtree[ind], segtree[ind ^ 1]); } } int range_max(int from, int to) { int max_ = 0; for (from += len, to += len; from < to; from >>= 1, to >>= 1) { if ((from & 1) != 0) { max_ = max(max_, segtree[from++]); } if ((to & 1) != 0) { max_ = max(max_, segtree[--to]); } } return max_; } }; // EndCodeSnip int main() { int n; cin >> n; vector<int> a(n); map<int, int> compressed; vector<int> best(n, 0); for (int i = 0; i < n; i++) { cin >> a[i]; } vector<int> val = a; val.push_back(0); for (int i = 0; i < n - 1; i++) { val.push_back(val[i] + 1); } sort(val.begin(), val.end()); val.erase(unique(val.begin(), val.end()), val.end()); for (int i = 0; i < val.size(); i++) { compressed[val[i]] = i; } for (int i = 1; i < n; i++) { best[i] = a[i - 1] + 1; } MaxSegTree before(2 * n); MaxSegTree after(2 * n); for (int i = 0; i < n; i++) { after.set(compressed[a[i]], after.range_max(0, compressed[a[i]]) + 1); after.set(compressed[best[i]], before.range_max(0, compressed[best[i]]) + 1); before.set(compressed[a[i]], before.range_max(0, compressed[a[i]]) + 1); } cout << after.range_max(0, 2 * n) << '\n'; return 0; }