Skip to Content

Longest Beautiful Subsequence

Explicación

Resolver este problema de una sola vez es extremadamente difícil: recorramos primero las subtareas.

Subtareas 1-2

Debido a las cotas pequeñas de NN, basta una solución O(N2)\mathcal{O}(N^2).

Definimos dp[i]\texttt{dp}[i] como la longitud de la LBS (subsecuencia bella más larga) más larga que termina en ii, y transk(y)\texttt{trans}_k(y) como todos los xx tales que x&yx \hspace{3pt} \& \hspace{3pt} y tiene kk bits. Transicionamos iterando sobre todos los j<ij < i donde ajtranski(ai)a_j \in \texttt{trans}_{k_i}(a_i). Para reconstruir una solución óptima definimos otro arreglo prv\texttt{prv}, donde prv[i]\texttt{prv}[i] es el índice más óptimo a incluir justo antes del índice ii.

Consideremos el primer caso de ejemplo: n=4n = 4, a={1,2,3,4}a = \{1, 2, 3, 4\}, y k={10,0,1,0}k = \{10, 0, 1, 0\}.

  • Caso base: dp[i]=1\texttt{dp}[i] = 1 para todo ii (cualquier número suelto forma una LBS) y prv[i]=i\texttt{prv}[i] = i.
  • dp[1]\texttt{dp}[1]: intentamos extender la LBS más larga que termina en a0=1a_0 = 1. Como bc[a0][a1]==k1\texttt{bc}[a_0][a_1] == k_1, nuestra nueva LBS es válida, y actualizamos dp[1]=dp[0]+1=2\texttt{dp}[1] = \texttt{dp}[0] + 1 = 2 y prv[1]=0\texttt{prv}[1] = 0.
  • dp[2]\texttt{dp}[2]: intentamos extender la LBS tanto desde a0a_0 como desde a1a_1.
    • a0a_0: bc[a0][a2]==k2dp[2]=dp[0]+1=2\texttt{bc}[a_0][a_2] == k_2 \rightarrow \texttt{dp}[2] = \texttt{dp}[0] + 1 = 2 y prv[2]=0\texttt{prv}[2] = 0
    • a1a_1: bc[a1][a2]==k2dp[2]=dp[1]+1=3\texttt{bc}[a_1][a_2] == k_2 \rightarrow \texttt{dp}[2] = \texttt{dp}[1] + 1 = 3 y prv[2]=1\texttt{prv}[2] = 1
    • Tomamos el mayor de los dos valores de DP, así que dp[2]=3\texttt{dp}[2] = 3 y prv[2]=1\texttt{prv}[2] = 1.
  • dp[3]\texttt{dp}[3]: intentamos extender desde a0a_0, a1a_1 y a2a_2.
    • a0a_0: bc[a0][a3]k3\texttt{bc}[a_0][a_3] \neq k_3 \rightarrow no hay transición
    • a1a_1: bc[a1][a3]==k3dp[3]=dp[1]+1=3\texttt{bc}[a_1][a_3] == k_3 \rightarrow \texttt{dp}[3] = \texttt{dp}[1] + 1 = 3 y prv[3]=1\texttt{prv}[3] = 1
    • a2a_2: bc[a2][a3]==k3dp[3]=dp[2]+1=4\texttt{bc}[a_2][a_3] == k_3 \rightarrow \texttt{dp}[3] = \texttt{dp}[2] + 1 = 4 y prv[3]=2\texttt{prv}[3] = 2
    • Tomando la mayor de las dos opciones, dp[3]=4\texttt{dp}[3] = 4 y prv[3]=2\texttt{prv}[3] = 2.
  • Respuesta final: len=max(dp[i]i[0,n))=dp[3]=4\texttt{len} = \max(\texttt{dp}[i]_{i \in [0, n)}) = \texttt{dp}[3] = \boxed{4}
  • Reconstrucción de una solución:
    • Usando nuestro arreglo prv\texttt{prv} y el índice final de la LBS, podemos hallar repetidamente el índice óptimo a incluir antes del actual.
    • elegimos ii tal que dp[i]=len=4i=3\texttt{dp}[i] = \texttt{len} = 4 \Rightarrow i = 3
    • inicializamos ansans como {i}={3}\{i\} = \{3\}.
    • Agregamos repetidamente prv[ans0]\texttt{prv}{[ans_0]} al frente de ansans.
      • ans={prv[3],3}={2,3}ans = \{\texttt{prv}[3], 3\} = \{2, 3\}
      • ans={prv[2],2,3}={1,2,3}ans = \{\texttt{prv}[2], 2, 3\} = \{1, 2, 3\}
      • ans={prv[1],1,2,3}={0,1,2,3}ans = \{\texttt{prv}[1], 1, 2, 3\} = \{0, 1, 2, 3\}
      • prv[0]=0\texttt{prv}[0] = 0, así que terminamos.
    • Por lo tanto, nuestra respuesta final (indexada desde cero) será ans={0,1,2,3}.ans = \boxed{\{0, 1, 2, 3\}}.

Implementación

Complejidad temporal: O(N2)\mathcal{O}(N^2)

Código
#include <bits/stdc++.h> using namespace std; int main() { int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } vector<int> k(n); for (int i = 0; i < n; i++) { cin >> k[i]; } vector<int> dp(n, 1); int ans = 1; // best_i: índice más óptimo en el que terminar int best_i = 0; // prv[i] = índice óptimo a incluir justo antes de i vector<int> prv(n); // inicialmente, prv[i] = i iota(prv.begin(), prv.end(), 0); for (int i = 1; i < n; i++) { for (int j = 0; j < i; j++) { // bc[a[j]][a[i]] == k[i] if (__builtin_popcount(a[j] & a[i]) == k[i]) { if (dp[j] + 1 > dp[i]) { prv[i] = j; dp[i] = dp[j] + 1; } } } if (dp[i] > ans) { ans = dp[i]; best_i = i; } } cout << ans << endl; vector<int> res; while (prv[best_i] != best_i) { res.push_back(best_i); best_i = prv[best_i]; } res.push_back(best_i); reverse(res.begin(), res.end()); for (int x : res) { cout << x + 1 << " "; } }

Subtarea 3

Aunque las cotas de NN ahora son mucho mayores (N105N \leq 10^5), el valor máximo de aia_i ahora es solo M=28M = 2^8. Podemos usar esto para optimizar la solución: en lugar de recorrer cada índice previo posible, recorremos todos los valores previos posibles.

Más concretamente, definimos dp1[i][x]\texttt{dp1}[i][x] como la longitud de la LBS más larga que termina con el valor xx (en lugar del índice) y es un subconjunto de los primeros ii números. Tenemos dos transiciones para cada índice ii:

  • Incluir aia_i: para calcular dp1[i][ai]\texttt{dp1}[i][a_i], transicionamos desde dp1[i1][x]\texttt{dp1}[i - 1][x] para todo xtranski(ai)x \in \texttt{trans}_{k_i}(a_i).
  • No incluir aia_i: dp1[i][x]=dp1[i1][x]\texttt{dp1}[i][x] = \texttt{dp1}[i - 1][x] para todo xMx \leq M.

Esto mejora nuestra complejidad temporal a O(NM)\mathcal{O}(NM). Notemos que, como dp1[i][x]\texttt{dp1}[i][x] solo depende de dp1[i1][...]\texttt{dp1}[i - 1][...], podemos eliminar la primera dimensión.

Consideremos de nuevo el primer caso de ejemplo: n=4n = 4, a={1,2,3,4}a = \{1, 2, 3, 4\}, y k={10,0,1,0}k = \{10, 0, 1, 0\}. El valor máximo es M=4M = 4.

  • Caso base: dp1[a0=1]={1,0}\texttt{dp1}[a_0 = 1] = \{1, 0\}.
    • todos los demás valores de DP empiezan en 0
    • prv[i]=i\texttt{prv}[i] = i
  • dp1[a1=2]={2,1}\texttt{dp1}[a_1 = 2] = \{2, 1\}.
    • recorremos todos los estados dp1[x]\texttt{dp1}[x] donde xMx \leq M y xx está en transki(ai)\texttt{trans}_{k_i}(a_i). Los valores de xx que cumplen estas condiciones son 00, 11 y 44.
    • De estos xx, dp1[x=1]\texttt{dp1}[x = 1] tiene el mayor valor de len, así que:
      • dp1[a1].len=dp1[x].len+1=2\texttt{dp1}[a_1]. \texttt{len} = \texttt{dp1}[x].\texttt{len} + 1 = 2.
      • dp1[a1].end=i=1\texttt{dp1}[a_1].\texttt{end} = i = 1.
      • prv[i]=dp1[x].end=0\texttt{prv}[i] = \texttt{dp1}[x].\texttt{end} = 0.
  • dp1[a2=3]={3,2}\texttt{dp1}[a_2 = 3] = \{3, 2\}.
    • aquí, x{1,2}x \in \{1, 2\} y dp1[x=2]\texttt{dp1}[x = 2] tiene el mayor valor, así que:
      • dp1[a2].len=dp1[x].len+1=3\texttt{dp1}[a_2]. \texttt{len} = \texttt{dp1}[x].\texttt{len} + 1 = 3.
      • dp1[a2].end=i=2\texttt{dp1}[a_2].\texttt{end} = i = 2.
      • prv[i]=dp1[x].end=1\texttt{prv}[i] = \texttt{dp1}[x].\texttt{end} = 1.
  • dp1[a3=4]={4,3}\texttt{dp1}[a_3 = 4] = \{4, 3\}.
    • aquí, x{0,1,2,3}x \in \{0, 1, 2, 3\} y dp1[x=3]\texttt{dp1}[x = 3] tiene el mayor valor, así que:
      • dp1[a3].len=dp1[x].len+1=4\texttt{dp1}[a_3]. \texttt{len} = \texttt{dp1}[x].\texttt{len} + 1 = 4.
      • dp1[a3].end=i=3\texttt{dp1}[a_3].\texttt{end} = i = 3.
      • prv[i]=dp1[x].end=2\texttt{prv}[i] = \texttt{dp1}[x].\texttt{end} = 2.
  • Respuesta final: el máximo valor len de DP, 4\boxed{4}.

Implementación

Complejidad temporal: O(NM)\mathcal{O}(NM)

Código
#include <bits/stdc++.h> using namespace std; using pii = pair<int, int>; const int B = 8; struct State { int len, end; }; int main() { // preprocess: // bc[i][j] = bit_count(i & j) vector<vector<int>> bc(1 << B, vector<int>(1 << B)); for (int i = 0; i < (1 << B); i++) { for (int j = 0; j < (1 << B); j++) { bc[i][j] = __builtin_popcount(i & j); } } int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) cin >> a[i]; vector<int> k(n); for (int i = 0; i < n; i++) cin >> k[i]; int ans = 1; // best_i: índice más óptimo en el que terminar int best_i = 0; vector<int> prv(n); // prv[i]: mejor índice a incluir antes de i iota(prv.begin(), prv.end(), 0); // prv[i] = i vector<State> dp1(1 << B, State{-1, -1}); for (int i = 0; i < n; i++) { if (dp1[a[i]].len == -1) { dp1[a[i]].len = 1; dp1[a[i]].end = i; } for (int j = 0; j < (1 << B); j++) { if (bc[j][a[i]] == k[i] && dp1[j].len + 1 > dp1[a[i]].len) { dp1[a[i]].len = dp1[j].len + 1; dp1[a[i]].end = i; prv[i] = dp1[j].end; } } if (dp1[a[i]].len > ans) { ans = dp1[a[i]].len; best_i = i; } } cout << ans << endl; vector<int> res; while (prv[best_i] != best_i) { res.push_back(best_i); best_i = prv[best_i]; } res.push_back(best_i); reverse(res.begin(), res.end()); for (int x : res) { cout << x + 1 << " "; } }

Subtarea 3 (solución alternativa)

En nuestra solución previa para esta Subtarea 3, el bucle de 0...M0...M parece repetitivo y motiva restringir de alguna forma el espacio de búsqueda de estados de DP. ¿Cómo podemos procesar rápido todos los xx en transki(ai)\texttt{trans}_{k_i}(a_i)?

La forma más extrema de optimizar las transiciones es incrustar las restricciones de xx directamente en el estado de DP.

Concretamente, definimos dp2[i][k]\texttt{dp2}[i][k] como el valor máximo de dp1[x]\texttt{dp1}[x] sobre todos los xx en transk(i)\texttt{trans}_k(i).

Para entender mejor esta redefinición de nuestro estado de DP, miremos de nuevo el ejemplo. Notemos que, en el cálculo de dp1[a3=4]\texttt{dp1}[a_3 = 4], procesamos todos los x{0,1,2,3}x \in \{0, 1, 2, 3\} uno por uno. Sin embargo, en nuestro estado recién definido, podemos obtener la solución más óptima sobre todos los valores xx simplemente consultando dp2[a3][k3]\texttt{dp2}[a_3][k_3].

En general, dp2[ai][ki]\texttt{dp2}[a_i][k_i] es ahora una transición O(1)\mathcal{O}(1) para hallar la LBS más larga que termina en el índice ii.

Sin embargo, cada estado dp2[i][k]\texttt{dp2}[i][k] ahora encapsula transk(i)M|\texttt{trans}_k(i)| \leq M de nuestros estados dp1\texttt{dp1} originales. De forma similar, una LBS con valor final fijo aia_i afecta MM estados dp2\texttt{dp2} comparado con un estado dp1\texttt{dp1}: a saber, todos los estados dp2[x][k]\texttt{dp2}[x][k] donde x[0,M)x \in [0, M) y k=bc[ai][x]k = \texttt{bc}[a_i][x].

Implementación (dp2)

Código
#include <bits/stdc++.h> using namespace std; using pii = pair<int, int>; const int B = 8; struct State { int len, end; }; int main() { // preprocess: // bc[i][j] = bit_count(i & j) vector<vector<int>> bc(1 << B, vector<int>(1 << B)); for (int i = 0; i < (1 << B); i++) { for (int j = 0; j < (1 << B); j++) { bc[i][j] = __builtin_popcount(i & j); } } int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) cin >> a[i]; vector<int> k(n); for (int i = 0; i < n; i++) cin >> k[i]; int ans = 1; // best_i: índice más óptimo en el que terminar int best_i = 0; vector<int> prv(n); // prv[i]: mejor índice a incluir antes de i iota(prv.begin(), prv.end(), 0); // prv[i] = i vector<vector<State>> dp2(1 << B, vector<State>(B, State{0, -1})); for (int i = 0; i < n; i++) { // longitud de la LBS más larga que termina en i int len = dp2[a[i]][k[i]].len + 1; if (dp2[a[i]][k[i]].end != -1) { prv[i] = dp2[a[i]][k[i]].end; } for (int j = 0; j < (1 << B); j++) { State &affected_state = dp2[j][bc[a[i]][j]]; if (len > affected_state.len) { affected_state = State{len, i}; } } if (len > ans) { ans = len; best_i = i; } } cout << ans << endl; vector<int> res; while (prv[best_i] != best_i) { res.push_back(best_i); best_i = prv[best_i]; } res.push_back(best_i); reverse(res.begin(), res.end()); for (int x : res) { cout << x + 1 << " "; } }

Subtarea 4 (solución completa)

En resumen, si xx es el último número de una LBS:

  • dp1\texttt{dp1} (solo un valor posible de xx) \rightarrow transición O(M)\mathcal{O}(M), O(1)\mathcal{O}(1) estados afectados
  • dp2\texttt{dp2} (hasta MM valores posibles de xx) \rightarrow transición O(1)\mathcal{O}(1), O(M)\mathcal{O}(M) estados afectados

Notemos que, no importa cómo definamos el estado de DP, estas dos complejidades siempre se multiplicarán a O(M)\mathcal{O}(M). Por lo tanto, queremos hallar una restricción tal que ambas complejidades sean O(M)\mathcal{O}(\sqrt{M}). Esto motiva usar una mezcla de dp1\texttt{dp1} y dp2\texttt{dp2}. Como dp12\texttt{dp1}2 ya restringe bits, parece natural considerar aplicar dp1\texttt{dp1} sobre algunos bits de xx y dp2\texttt{dp2} sobre el resto.

Concretamente, sea lx=l(x,b)l_x = \texttt{l}(x, b) el número representado por los bb bits más a la izquierda de xx y rx=r(x,b)r_x = \texttt{r}(x, b) el número representado por los \texttt{bit\\_count}(x) - b bits más a la derecha. En nuestro nuevo estado de DP dp[i][j][k]\texttt{dp}[i][j][k], vamos a:

  • fijar lx=il_x = i (esta es la parte dp1\texttt{dp1} de nuestro nuevo estado)
  • restringir rxr_x de modo que rxtransk(j)bc[rx][j]=kr_x \in \texttt{trans}_k(j) \Rightarrow \texttt{bc}[r_x][j] = k (esta es la parte dp2\texttt{dp2} de nuestro nuevo estado)

¿Cuáles son ahora las complejidades temporales de transición y estados afectados?

Transición

Digamos que estamos procesando aia_i. Si tenemos xx tal que xtranski(ai)x \in \texttt{trans}_{k_i}(a_i), entonces bc[x][ai]=ki\texttt{bc}[x][a_i] = k_i. Al considerar los lados izquierdo y derecho de xx y aia_i por separado, podemos reescribir bc[x][ai]\texttt{bc}[x][a_i] como bc[lx][lai]+bc[rx][rai]=ki\texttt{bc}[l_x][l_{a_i}] + \texttt{bc}[r_x][r_{a_i}] = k_i.


Ejemplo: xx = 01010, aia_i = 01101, bb = 3

01010 & 01101 = 01000 -> 1 010 & 011 = 010 -> 1 10 & 01 = 00 -> 0

1+0=11 + 0 = \boxed1.


Como nuestro estado de DP dp[l][r][k]\texttt{dp}[l][r][k] fija lx=ll_x = l, deberíamos reescribir esta ecuación para despejar rxr_x: bc[rx][rai]=kibc[lx][lai]\texttt{bc}[r_x][r_{a_i}] = k_i - \texttt{bc}[l_x][l_{a_i}]. Notemos que esta restricción coincide con nuestra restricción de dp\texttt{dp} de bc[rx][j]=k\texttt{bc}[r_x][j] = k, así que tenemos r=rair = r_{a_i} y k=kibc[lx][lai]=kibc[x][lai]k = k_i - \texttt{bc}[l_x][l_{a_i}] = k_i - \texttt{bc}[x][l_{a_i}]. En cuanto a l=lx=l(x,b)l = l_x = \texttt{l}(x, b), simplemente enumeramos las 2b2^b posibilidades; por lo tanto, i[0,2b)i \in [0, 2^b).

En resumen, las transiciones hacia aia_i son todos los estados dp[l][rai][kibc[l][lai]]\texttt{dp}[l][r_{a_i}][k_i - \texttt{bc}[l][l_{a_i}]] donde l[0,2b)l \in [0, 2^b).

Como la única variable en el estado es ll, la complejidad de la transición es la cantidad de opciones para ll, que es 2b\boxed{2^b}.

Estados afectados

Ahora tenemos la longitud de la LBS más larga que termina en aia_i. ¿Qué estados dp[l][r][k]\texttt{dp}[l][r][k] afecta esto?

Empezando por lo obvio, l=lail = l_{a_i}. También tenemos la restricción de que raitransk(r)k=bc[rai][r]r_{a_i} \in \texttt{trans}_k(r) \Rightarrow k = \texttt{bc}[r_{a_i}][r]. Como rair_{a_i} es una constante, kk depende únicamente de rr.

En resumen, los estados afectados por aia_i son todos los estados dp[lai][r][bc[rai][r]]\texttt{dp}[l_{a_i}][r][\texttt{bc}[r_{a_i}][r]].

rr tiene que tener la misma cantidad de bits que rai=r(ai,b)r_{a_i} = \texttt{r}(a_i, b), que tiene log2Mb\log_2{M} - b bits. Por lo tanto, rr tiene 2log2Mb=M2b2^{\log_2M - b} = \boxed{\frac{M}{2^b}} posibilidades.

Para resumirlo todo, la complejidad de la transición es O(2b)\mathcal{O}(2^b) y la complejidad de los estados afectados es O(M2b)\mathcal{O}(\frac{M}{2^b}). Para minimizar la suma de estas complejidades, deberíamos igualarlas. Como M=220M = 2 ^{20}, hallamos que el valor óptimo de bb es 10\boxed{10}.

Implementación

En la implementación de abajo:

  • l(x)\texttt{l}(x) denota los 10 bits más a la izquierda de xx
  • r(x)\texttt{r}(x) denota los 10 bits más a la derecha de xx

Complejidad temporal: O(NM)\mathcal{O}(N\sqrt{M})

Código
#include <bits/stdc++.h> using namespace std; using pii = pair<int, int>; const int N = 1e5; const int B = 10; int bc[1 << B][1 << B]; // bc[i][j] = bit_count(i & j) struct State { int len; int end; } dp[1 << B][1 << B][B + 1]; int main() { // preprocess: for (int i = 0; i < (1 << B); i++) { for (int j = 0; j < (1 << B); j++) { bc[i][j] = __builtin_popcount(i & j); } } int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) cin >> a[i]; vector<int> k(n); for (int i = 0; i < n; i++) cin >> k[i]; int ans = 1; // best_i: índice más óptimo en el que terminar int best_i = 0; vector<int> prv(n); // prv[i]: mejor índice a incluir antes de i iota(prv.begin(), prv.end(), 0); // prv[i] = i for (int i = 0; i < n; i++) { int l = a[i] >> B; // l(a[i]) int r = a[i] % (1 << B); // r(a[i]) int lbs = 1; // longitud de la lbs más larga que termina en i // enumeramos todas las posibilidades para l(prev_num) for (int j = 0; j < (1 << B); j++) { // aquí, usamos el hecho de que // bc[x][y] = bc[l(x)][l(y)] + bc[r(x)][r(y)], // o bc[r(x)][r(y)] = bc[x][y] - bc[l(x)][l(y)] // valor requerido de bc[r(x)][r(y)] int needed = k[i] - bc[j][l]; if (needed < 0 || needed > B) continue; if (dp[j][r][needed].len + 1 > lbs) { lbs = dp[j][r][needed].len + 1; prv[i] = dp[j][r][needed].end; } } if (lbs > ans) { ans = lbs; best_i = i; } // actualizamos todas las respuestas que a[i] afecta for (int j = 0; j < (1 << B); j++) { State &new_state = dp[l][j][bc[r][j]]; if (lbs > new_state.len) { new_state.len = lbs; new_state.end = i; } } } cout << ans << endl; vector<int> res; while (prv[best_i] != best_i) { res.push_back(best_i); best_i = prv[best_i]; } res.push_back(best_i); reverse(res.begin(), res.end()); for (int x : res) { cout << x + 1 << " "; } }