Diese Antwort wurde von meiner Antwort auf eine ähnliche Frage bei Computer Science Stackexchange geändert.
Das LIS-Problem mit höchstens k Ausnahmen lässt einen O (n log² n) -Algorithmus unter Verwendung der Lagrange-Relaxation zu. Wenn k größer als log n ist, verbessert sich dies asymptotisch auf dem O (nk log n) DP, was wir auch kurz erklären werden.
DP [a] [b] bezeichne die Länge der am längsten ansteigenden Teilsequenz mit höchstens b Ausnahmen (Positionen, an denen die vorherige Ganzzahl größer als die nächste ist), die am Element b a enden . Dieser DP ist nicht am Algorithmus beteiligt, aber seine Definition erleichtert das Testen des Algorithmus.
Der Einfachheit halber nehmen wir an, dass alle Elemente unterschiedlich sind und dass das letzte Element im Array das Maximum ist. Beachten Sie, dass dies uns nicht einschränkt, da wir einfach m / 2n zum m-ten Erscheinungsbild jeder Zahl hinzufügen und dem Array unendlich hinzufügen und eins von der Antwort subtrahieren können. Sei V die Permutation, für die 1 <= V [i] <= n der Wert des i-ten Elements ist.
Um das Problem in O (nk log n) zu lösen, behalten wir die Invariante bei, dass DP [a] [b] für b <j berechnet wurde. Schleife j von 0 nach k bei der j-ten Iteration, in der DP [a] [j] für alle a berechnet wird. Schleifen Sie dazu i von 1 nach n. Wir behalten das Maximum von DP [x] [j-1] über x <i und eine maximale Präfixdatenstruktur bei, die am Index i DP [x] [j] an Position V [x] für x <i und 0 hat an jeder anderen Position.
Wir haben DP [i] [j] = 1 + max (DP [i '] [j], DP [x] [j-1]), wobei wir über i', x <i, V [i '] <gehen V [i]. Das Präfixmaximum von DP [x] [j-1] gibt uns das Maximum der Terme des zweiten Typs, und das Abfragen der maximalen Präfixdatenstruktur für das Präfix [0, V [i]] gibt uns das Maximum der Terme des ersten Art. Aktualisieren Sie dann die Datenstruktur für das maximale Präfix und das maximale Präfix.
Hier ist eine C ++ - Implementierung des Algorithmus. Beachten Sie, dass diese Implementierung nicht davon ausgeht, dass das letzte Element des Arrays maximal ist oder dass das Array keine Duplikate enthält.
#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';
}
Nun kehren wir zum O (n log² n) -Algorithmus zurück. Wählen Sie eine Ganzzahl 0 <= r <= n. Definieren Sie DP '[a] [r] = max (DP [a] [b] - rb), wobei das Maximum über b, MAXB [a] [r] als das Maximum b übernommen wird, so dass DP' [a] [ r] = DP [a] [b] - rb und MINB [a] [r] ähnlich wie das Minimum wie b. Wir werden zeigen, dass DP [a] [k] = DP '[a] [r] + rk genau dann ist, wenn MINB [a] [r] <= k <= MAXB [a] [r]. Weiterhin werden wir zeigen, dass für jedes k ein r existiert, für das diese Ungleichung gilt.
Man beachte, dass MINB [a] [r]> = MINB [a] [r '] und MAXB [a] [r]> = MAXB [a] [r'], wenn r <r ', also wenn wir die beiden beanspruchten annehmen Als Ergebnis können wir eine binäre Suche nach dem r durchführen und O (log n) -Werte versuchen. Daher erreichen wir die Komplexität O (n log² n), wenn wir DP ', MINB und MAXB in O (n log n) berechnen können.
Dazu benötigen wir einen Segmentbaum, der die Tupel P [i] = (v_i, low_i, high_i) speichert und die folgenden Operationen unterstützt:
Bestimmen Sie bei gegebenem Bereich [a, b] den Maximalwert in diesem Bereich (Maximum v_i, a <= i <= b) und das Minimum niedrig und Maximum hoch gepaart mit diesem Wert im Bereich.
Stellen Sie den Wert des Tupels P [i] ein.
Dies ist einfach zu implementieren, da die Komplexität O (log n) pro Operation vorausgesetzt wird, dass Sie mit den Segmentbäumen vertraut sind. Einzelheiten finden Sie in der folgenden Implementierung des Algorithmus.
Wir werden nun zeigen, wie DP ', MINB und MAXB in O (n log n) berechnet werden. Fix r. Erstellen Sie den Segmentbaum, der anfänglich n + 1 Nullwerte enthält (-INF, INF, -INF). Wir behaupten, dass P [V [j]] = (DP '[j], MINB [j], MAXB [j]) für j kleiner als die aktuelle Position i ist. Setze DP '[0] = 0, MINB [0] = 0 und MAXB [0] auf 0, wenn r> 0, andernfalls auf INF und P [0] = (DP' [0], MINB [0], MAXB [ 0]).
Schleife i von 1 bis n. Es gibt zwei Arten von Teilsequenzen, die mit i enden: diejenigen, bei denen das vorherige Element größer als V [i] ist, und diejenigen, bei denen es kleiner als V [i] ist. Um die zweite Art zu berücksichtigen, fragen Sie den Segmentbaum im Bereich [0, V [i]] ab. Das Ergebnis sei (v_1, low_1, high_1). Setze off1 = (v_1 + 1, low_1, high_1). Fragen Sie für die erste Art den Segmentbaum im Bereich [V [i], n] ab. Das Ergebnis sei (v_2, low_2, high_2). Setzen Sie off2 = (v_2 + 1 - r, low_2 + 1, high_2 + 1), wobei wir die Strafe von r für das Erstellen einer Ausnahme erhalten.
Dann kombinieren wir off1 und off2 zu off. Wenn off1.v> off2.v set off = off1 und wenn off2.v> off1.v set off = off2. Andernfalls setzen Sie = (off1.v, min (off1.low, off2.low), max (off1.high, off2.high)). Dann setze DP '[i] = off.v, MINB [i] = off.low, MAXB [i] = off.high und P [i] = off.
Da wir bei jedem i zwei Segmentbaumabfragen durchführen, dauert dies insgesamt O (n log n). Es ist leicht durch Induktion zu beweisen, dass wir die korrekten Werte DP ', MINB und MAXB berechnen.
Kurz gesagt lautet der Algorithmus:
Vorverarbeiten, Werte so ändern, dass sie eine Permutation bilden, und der letzte Wert ist der größte Wert.
Binäre Suche nach dem richtigen r mit Anfangsgrenzen 0 <= r <= n
Initialisieren Sie den Segmentbaum mit Nullwerten, setzen Sie DP '[0], MINB [0] und MAXB [0].
Schleife von i = 1 nach n in Schritt i
- Abfragebereiche [0, V [i]] und [V [i], n] des Segmentbaums,
- Berechnen von DP '[i], MINB [i] und MAXB [i] basierend auf diesen Abfragen und
- Setzen des Wertes an Position V [i] im Segmentbaum auf das Tupel (DP '[i], MINB [i], MAXB [i]).
Wenn MINB [n] [r] <= k <= MAXB [n] [r], geben Sie DP '[n] [r] + kr - 1 zurück.
Andernfalls ist, wenn MAXB [n] [r] <k ist, das korrekte r kleiner als das aktuelle r. Wenn MINB [n] [r]> k ist, ist das korrekte r größer als das aktuelle r. Aktualisieren Sie die Grenzen für r und kehren Sie zu Schritt 1 zurück.
Hier ist eine C ++ - Implementierung für diesen Algorithmus. Es findet auch die optimale Teilsequenz.
#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';
}
Wir werden nun die beiden Behauptungen beweisen. Das wollen wir beweisen
DP '[a] [r] = DP [a] [b] - rb genau dann, wenn MINB [a] [r] <= b <= MAXB [a] [r]
Für alle a, k existiert eine ganze Zahl r, 0 <= r <= n, so dass MINB [a] [r] <= k <= MAXB [a] [r]
Beides ergibt sich aus der Konkavität des Problems. Konkavität bedeutet, dass DP [a] [k + 2] - DP [a] [k + 1] <= DP [a] [k + 1] - DP [a] [k] für alle a, k. Dies ist intuitiv: Je mehr Ausnahmen wir machen dürfen, desto weniger hilft es uns, eine weitere zuzulassen.
Fix a und r. Setze f (b) = DP [a] [b] - rb und d (b) = f (b + 1) - f (b). Wir haben d (k + 1) <= d (k) aus der Konkavität des Problems. Angenommen, x <y und f (x) = f (y)> = f (i) für alle i. Daher ist d (x) <= 0, also d (i) <= 0 für i in [x, y). Aber f (y) = f (x) + d (x) + d (x + 1) + ... + d (y - 1), daher ist d (i) = 0 für i in [x, y). Daher ist f (y) = f (x) = f (i) für i in [x, y]. Dies beweist den ersten Anspruch.
Um das zweite zu beweisen, setze r = DP [a] [k + 1] - DP [a] [k] und definiere f, d wie zuvor. Dann ist d (k) = 0, daher ist d (i)> = 0 für i <k und d (i) <= 0 für i> k, daher ist f (k) wie gewünscht maximal.
Konkavität zu beweisen ist schwieriger. Einen Beweis finden Sie in meiner Antwort unter cs.stackexchange.