Bei einer symmetrischen reellen Matrix gibt es einen Algorithmus, der die Summe berechnet) über alle 1 \ leq i <j <k \ leq n mit Zeitkomplexität besser als O (n ^ 3) ?
Bei einer symmetrischen reellen Matrix gibt es einen Algorithmus, der die Summe berechnet) über alle 1 \ leq i <j <k \ leq n mit Zeitkomplexität besser als O (n ^ 3) ?
Antworten:
Es gibt einen recht praktischen Ansatz, der in funktioniert , wobei die Anzahl der Bits im Prozessorwort ist. Die Hauptidee ist, dass Sie die Elemente der Matrix nacheinander in aufsteigender Reihenfolge durchlaufen (Bindungen willkürlich unterbrechen) und "einschalten". Betrachten Sie den Moment, in dem das größte Element eines Tripels ist. Nehmen wir der Einfachheit halber an, dass das besagte Element . Es ist natürlich, den Wert des Tripels jetzt zur Antwort hinzuzufügen, wenn das letzte Element eingeschaltet ist. Wir müssen also die Anzahl der möglichen , so dass undsind bereits eingeschaltet (das wäre die Anzahl der Tripel, hier ist das größte Element, daher wurden sie gerade vollständig eingeschaltet). Hier können wir die naive -Implementierung durch Bitoptimierung beschleunigen .
Einzelheiten finden Sie in der folgenden Implementierung in C ++ 11, die für , funktionieren sollte (es ist nicht sehr optimiert; es schlägt jedoch immer noch die naive Summierung für mit großem Abstand, zumindest auf meinem Computer).
// code is not very elegant,
// but should be understandable
// here the matrix a has dimensions n x n
// a has to be symmetric!
int64_t solve (int n, const vector<vector<int32_t>> &a)
{
std::vector<boost::dynamic_bitset<int64_t>> mat
(n, boost::dynamic_bitset<int64_t>(n));
vector<pair<int, int>> order;
for (int j = 1; j < n; j++)
for (int i = 0; i < j; i++)
order.emplace_back(i, j);
sort(order.begin(), order.end(),
[&] (const pair<int, int> &l, const pair<int, int> &r)
{return a[l.first][l.second] < a[r.first][r.second];});
int64_t ans = 0;
for (const auto &position : order)
{
int i, j;
tie (i, j) = position;
mat[i][j] = mat[j][i] = 1;
// here it is important that conditions
// mat[i][i] = 0 and mat[j][j] = 0 always hold
ans += (mat[i] & mat[j]).count() * int64_t(a[i][j]);
}
return ans;
}
Wenn Sie in Betracht ziehen, Bitoptimierungen zu verwenden, können Sie hier die vier russische Methoden verwenden, um das gleiche Ergebnis zu erzielen. Dies ergibt einen -Algorithmus, der weniger praktisch sein sollte (da auf den meisten modernen Hardwarekomponenten ziemlich groß ist). ist aber theoretisch besser. In der Tat wählen wir und behalten jede Zeile der Matrix als Array von Ganzzahlen von bis , wobei die te Zahl in ist Das Array entspricht den Bits der Zeile im Bereich von einschließlich bis exklusiv in-Indexation. Wir können die Skalarprodukte von jeweils zwei solchen Blöcken in -Zeit vorberechnen . Das Aktualisieren einer Position in der Matrix ist schnell, da wir nur eine Ganzzahl ändern. Um das Skalarprodukt der Zeilen und iterieren Sie einfach über Arrays, die diesen Zeilen entsprechen. Suchen Sie nach Skalarprodukten der entsprechenden Blöcke in der Tabelle und fassen Sie die erhaltenen Produkte zusammen.
Der obige Absatz nimmt an, dass Operationen mit ganzen Zahlen nehmen Zeit. Dies ist eine weit verbreitete Annahme , da sie die Vergleichsgeschwindigkeit der Algorithmen normalerweise nicht ändert (wenn wir diese Annahme beispielsweise nicht verwenden, funktioniert die Brute-Force-Methode tatsächlich in der Zeit (hier) Wir messen die Zeit in Bitoperationen), wenn ganzzahlige Werte mit absoluten Werten von mindestens bis zu für eine Konstante annimmt (und wir das Problem ansonsten mit lösen können) Matrixmultiplikationen ohnehin), jedoch verwendet die oben vorgeschlagene Vier-Russen-Methode Operationen mit Nummern der Größe in diesem Fall; es werden also -Bitoperationen durchgeführt, die trotz der Änderung des Modells immer noch besser sind als Brute Force.
Die Frage nach der Existenz des -Ansatzes ist jedoch immer noch interessant.
Die in dieser Antwort vorgestellten Techniken (Bitoptimierungen und Vier-Russen-Methode) sind keineswegs originell und werden hier der Vollständigkeit der Darstellung halber vorgestellt. Es war jedoch nicht trivial, einen Weg zu finden, sie anzuwenden.
mat[i]
mat[j]
mat
was wichtig zu sein scheint. Ich verstehe, wie es definiert werden könnte, aber ich frage mich, ob (mat[i] & mat[j]).count()
es mit jedem STL-Container wie gewünscht funktionieren würde.
mat
- ich denke, wir müssen verwenden std::vector<boost::dynamic_bitset<int64_t>>
.
mat
: Ja, ich hatte eigentlich ein Standard-Bitset im Sinn, aber boost::dynamic_bitset
in diesem Fall ist es sogar noch besser, da seine Größe nicht konstant zur Kompilierungszeit sein muss. Wird die Antwort bearbeiten, um dieses Detail hinzuzufügen und den Ansatz der vier Russen zu verdeutlichen.