Esta resposta foi modificada da minha resposta para uma pergunta semelhante no Computer Science Stackexchange.
O problema do LIS com no máximo k exceções admite um algoritmo O (n log² n) usando relaxação Lagrangiana. Quando k é maior que log n, isso melhora assintoticamente no O (nk log n) DP, o que também explicaremos brevemente.
Seja DP [a] [b] denotando o comprimento da subsequência crescente mais longa com no máximo b exceções (posições em que o número inteiro anterior é maior que o próximo) terminando no elemento b a. Esse DP não está envolvido no algoritmo, mas sua definição facilita a prova do algoritmo.
Por conveniência, assumiremos que todos os elementos são distintos e que o último elemento da matriz é o máximo. Observe que isso não nos limita, pois podemos adicionar m / 2n à m-ésima aparência de cada número e acrescentar infinito à matriz e subtrair um da resposta. Seja V a permutação pela qual 1 <= V [i] <= n é o valor do i-ésimo elemento.
Para resolver o problema em O (nk log n), mantemos a invariante de que DP [a] [b] foi calculado para b <j. Loop j de 0 a k, na j-ésima iteração calculando DP [a] [j] para todos a. Para fazer isso, faça um loop i de 1 a n. Mantemos o máximo de DP [x] [j-1] acima de x <ie um prefixo máximo de estrutura de dados que no índice i terá DP [x] [j] na posição V [x] para x <ie 0 em qualquer outra posição.
Temos DP [i] [j] = 1 + max (DP [i '] [j], DP [x] [j-1]) onde examinamos i', x <i, V [i '] < V [i]. O prefixo máximo de DP [x] [j-1] nos fornece o máximo de termos do segundo tipo, e a consulta da estrutura de dados máxima do prefixo para o prefixo [0, V [i]] nos fornece o máximo de termos do primeiro tipo. Atualize o prefixo máximo e a estrutura máxima de dados do prefixo.
Aqui está uma implementação do algoritmo em C ++. Observe que esta implementação não pressupõe que o último elemento da matriz seja o máximo ou que a matriz não contém duplicatas.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// Fenwick tree for prefix maximum queries
class Fenwick {
private:
vector<int> val;
public:
Fenwick(int n) : val(n+1, 0) {}
// Sets value at position i to maximum of its current value and
void inc(int i, int v) {
for (++i; i < val.size(); i += i & -i) val[i] = max(val[i], v);
}
// Calculates prefix maximum up to index i
int get(int i) {
int res = 0;
for (++i; i > 0; i -= i & -i) res = max(res, val[i]);
return res;
}
};
// Binary searches index of v from sorted vector
int bins(const vector<int>& vec, int v) {
int low = 0;
int high = (int)vec.size() - 1;
while(low != high) {
int mid = (low + high) / 2;
if (vec[mid] < v) low = mid + 1;
else high = mid;
}
return low;
}
// Compresses the range of values to [0, m), and returns m
int compress(vector<int>& vec) {
vector<int> ord = vec;
sort(ord.begin(), ord.end());
ord.erase(unique(ord.begin(), ord.end()), ord.end());
for (int& v : vec) v = bins(ord, v);
return ord.size();
}
// Returns length of longest strictly increasing subsequence with at most k exceptions
int lisExc(int k, vector<int> vec) {
int n = vec.size();
int m = compress(vec);
vector<int> dp(n, 0);
for (int j = 0;; ++j) {
Fenwick fenw(m+1); // longest subsequence with at most j exceptions ending at this value
int max_exc = 0; // longest subsequence with at most j-1 exceptions ending before this
for (int i = 0; i < n; ++i) {
int off = 1 + max(max_exc, fenw.get(vec[i]));
max_exc = max(max_exc, dp[i]);
dp[i] = off;
fenw.inc(vec[i]+1, off);
}
if (j == k) return fenw.get(m);
}
}
int main() {
int n, k;
cin >> n >> k;
vector<int> vec(n);
for (int i = 0; i < n; ++i) cin >> vec[i];
int res = lisExc(k, vec);
cout << res << '\n';
}
Agora retornaremos ao algoritmo O (n log² n). Selecione algum número inteiro 0 <= r <= n. Defina DP '[a] [r] = max (DP [a] [b] - rb), onde o máximo é retomado b, MAXB [a] [r] como o máximo b, de modo que DP' [a] [ r] = DP [a] [b] - rb e MINB [a] [r] da mesma forma que o mínimo tal b. Mostraremos que DP [a] [k] = DP '[a] [r] + rk se e somente se MINB [a] [r] <= k <= MAXB [a] [r]. Além disso, mostraremos que para qualquer k existe um r para o qual essa desigualdade é válida.
Observe que MINB [a] [r]> = MINB [a] [r '] e MAXB [a] [r]> = MAXB [a] [r'] se r <r '; portanto, se assumirmos as duas reivindicações resultados, podemos fazer uma pesquisa binária para r, tentando valores O (log n). Portanto, atingimos a complexidade O (n log² n) se pudermos calcular DP ', MINB e MAXB em O (n log n).
Para fazer isso, precisaremos de uma árvore de segmentos que armazene tuplas P [i] = (v_i, baixo_i, alto_i) e suporte as seguintes operações:
Dado um intervalo [a, b], encontre o valor máximo nesse intervalo (máximo v_i, a <= i <= b) e o mínimo baixo e o máximo alto emparelhados com esse valor no intervalo.
Defina o valor da tupla P [i]
É fácil de implementar com complexidade O (log n) tempo por operação, assumindo alguma familiaridade com as árvores de segmentos. Você pode consultar a implementação do algoritmo abaixo para obter detalhes.
Vamos agora mostrar como calcular DP ', MINB e MAXB em O (n log n). Corrigir r. Crie a árvore de segmentos inicialmente contendo n + 1 valores nulos (-INF, INF, -INF). Mantemos que P [V [j]] = (DP '[j], MINB [j], MAXB [j]) para j menor que a posição atual i. Defina DP '[0] = 0, MINB [0] = 0 e MAXB [0] para 0 se r> 0, caso contrário, para INF e P [0] = (DP' [0], MINB [0], MAXB [ 0]).
Loop i de 1 a n. Existem dois tipos de subsequências que terminam em i: aquelas em que o elemento anterior é maior que V [i] e aquelas em que é menor que V [i]. Para explicar o segundo tipo, consulte a árvore de segmentos no intervalo [0, V [i]]. Seja o resultado (v_1, baixo_1, alto_1). Desativar1 = (v_1 + 1, baixo_1, alto_1). Para o primeiro tipo, consulte a árvore de segmentos no intervalo [V [i], n]. Seja o resultado (v_2, baixo_2, alto_2). Defina off2 = (v_2 + 1 - r, baixo_2 + 1, alto_2 + 1), onde incorremos na penalidade de r por criar uma exceção.
Então combinamos off1 e off2 em off. Se off1.v> off2.v ativar = off1, e se off2.v> off1.v ativar = off2. Caso contrário, defina = (off1.v, min (off1.low, off2.low), max (off1.high, off2.high)). Em seguida, defina DP '[i] = desligado.v, MINB [i] = desligado.baixo, MAXB [i] = desligado.alta e P [i] = desligado.
Como fazemos duas consultas em árvore de segmento a cada i, isso leva tempo O (n log n) no total. É fácil provar por indução que calculamos os valores corretos DP ', MINB e MAXB.
Então, resumindo, o algoritmo é:
Pré-processe, modificando valores para que eles formem uma permutação e o último valor seja o maior valor.
Pesquisa binária pelo r correto, com limites iniciais 0 <= r <= n
Inicialize a árvore de segmentos com valores nulos, defina DP '[0], MINB [0] e MAXB [0].
Loop de i = 1 para n, na etapa i
- Consultar intervalos [0, V [i]] e [V [i], n] da árvore de segmentos,
- calcular DP '[i], MINB [i] e MAXB [i] com base nessas consultas, e
- ajustando o valor na posição V [i] na árvore de segmentos para a tupla (DP '[i], MINB [i], MAXB [i]).
Se MINB [n] [r] <= k <= MAXB [n] [r], retorne DP '[n] [r] + kr - 1.
Caso contrário, se MAXB [n] [r] <k, o r correto for menor que o atual r. Se MINB [n] [r]> k, o r correto for maior que o atual r. Atualize os limites em re retorne à etapa 1.
Aqui está uma implementação C ++ para esse algoritmo. Ele também encontra a subsequência ideal.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
using ll = long long;
const int INF = 2 * (int)1e9;
pair<ll, pair<int, int>> combine(pair<ll, pair<int, int>> le, pair<ll, pair<int, int>> ri) {
if (le.first < ri.first) swap(le, ri);
if (ri.first == le.first) {
le.second.first = min(le.second.first, ri.second.first);
le.second.second = max(le.second.second, ri.second.second);
}
return le;
}
// Specialised range maximum segment tree
class SegTree {
private:
vector<pair<ll, pair<int, int>>> seg;
int h = 1;
pair<ll, pair<int, int>> recGet(int a, int b, int i, int le, int ri) const {
if (ri <= a || b <= le) return {-INF, {INF, -INF}};
else if (a <= le && ri <= b) return seg[i];
else return combine(recGet(a, b, 2*i, le, (le+ri)/2), recGet(a, b, 2*i+1, (le+ri)/2, ri));
}
public:
SegTree(int n) {
while(h < n) h *= 2;
seg.resize(2*h, {-INF, {INF, -INF}});
}
void set(int i, pair<ll, pair<int, int>> off) {
seg[i+h] = combine(seg[i+h], off);
for (i += h; i > 1; i /= 2) seg[i/2] = combine(seg[i], seg[i^1]);
}
pair<ll, pair<int, int>> get(int a, int b) const {
return recGet(a, b+1, 1, 0, h);
}
};
// Binary searches index of v from sorted vector
int bins(const vector<int>& vec, int v) {
int low = 0;
int high = (int)vec.size() - 1;
while(low != high) {
int mid = (low + high) / 2;
if (vec[mid] < v) low = mid + 1;
else high = mid;
}
return low;
}
// Finds longest strictly increasing subsequence with at most k exceptions in O(n log^2 n)
vector<int> lisExc(int k, vector<int> vec) {
// Compress values
vector<int> ord = vec;
sort(ord.begin(), ord.end());
ord.erase(unique(ord.begin(), ord.end()), ord.end());
for (auto& v : vec) v = bins(ord, v) + 1;
// Binary search lambda
int n = vec.size();
int m = ord.size() + 1;
int lambda_0 = 0;
int lambda_1 = n;
while(true) {
int lambda = (lambda_0 + lambda_1) / 2;
SegTree seg(m);
if (lambda > 0) seg.set(0, {0, {0, 0}});
else seg.set(0, {0, {0, INF}});
// Calculate DP
vector<pair<ll, pair<int, int>>> dp(n);
for (int i = 0; i < n; ++i) {
auto off0 = seg.get(0, vec[i]-1); // previous < this
off0.first += 1;
auto off1 = seg.get(vec[i], m-1); // previous >= this
off1.first += 1 - lambda;
off1.second.first += 1;
off1.second.second += 1;
dp[i] = combine(off0, off1);
seg.set(vec[i], dp[i]);
}
// Is min_b <= k <= max_b?
auto off = seg.get(0, m-1);
if (off.second.second < k) {
lambda_1 = lambda - 1;
} else if (off.second.first > k) {
lambda_0 = lambda + 1;
} else {
// Construct solution
ll r = off.first + 1;
int v = m;
int b = k;
vector<int> res;
for (int i = n-1; i >= 0; --i) {
if (vec[i] < v) {
if (r == dp[i].first + 1 && dp[i].second.first <= b && b <= dp[i].second.second) {
res.push_back(i);
r -= 1;
v = vec[i];
}
} else {
if (r == dp[i].first + 1 - lambda && dp[i].second.first <= b-1 && b-1 <= dp[i].second.second) {
res.push_back(i);
r -= 1 - lambda;
v = vec[i];
--b;
}
}
}
reverse(res.begin(), res.end());
return res;
}
}
}
int main() {
int n, k;
cin >> n >> k;
vector<int> vec(n);
for (int i = 0; i < n; ++i) cin >> vec[i];
vector<int> ans = lisExc(k, vec);
for (auto i : ans) cout << i+1 << ' ';
cout << '\n';
}
Agora provaremos as duas reivindicações. Queremos provar que
DP '[a] [r] = DP [a] [b] - rb se e somente se MINB [a] [r] <= b <= MAXB [a] [r]
Para todo a, k existe um número inteiro r, 0 <= r <= n, de modo que MINB [a] [r] <= k <= MAXB [a] [r]
Ambos seguem a concavidade do problema. Concavidade significa que DP [a] [k + 2] - DP [a] [k + 1] <= DP [a] [k + 1] - DP [a] [k] para todos os a, k. Isso é intuitivo: quanto mais exceções tivermos permissão para fazer, menor será a permissão de mais uma.
Corrija a e r. Defina f (b) = DP [a] [b] - rb e d (b) = f (b + 1) - f (b). Temos d (k + 1) <= d (k) da concavidade do problema. Suponha x <yef (x) = f (y)> = f (i) para todos i. Portanto, d (x) <= 0, portanto d (i) <= 0 para i em [x, y). Mas f (y) = f (x) + d (x) + d (x + 1) + ... + d (y - 1), portanto d (i) = 0 para i em [x, y). Portanto, f (y) = f (x) = f (i) para i em [x, y]. Isso prova a primeira reivindicação.
Para provar o segundo, defina r = DP [a] [k + 1] - DP [a] [k] e defina f, d como anteriormente. Então d (k) = 0, portanto d (i)> = 0 para i <ke ed (i) <= 0 para i> k, portanto, f (k) é o máximo, conforme desejado.
Provar concavidade é mais difícil. Para uma prova, veja minha resposta em cs.stackexchange.