Biorąc pod uwagę symetryczną rzeczywistą macierz , czy istnieje algorytm, który oblicza sumę ponad wszystkimi 1 \ leq i <j <k \ leq n ze złożonością czasową lepszą niż O (n ^ 3) ?
Biorąc pod uwagę symetryczną rzeczywistą macierz , czy istnieje algorytm, który oblicza sumę ponad wszystkimi 1 \ leq i <j <k \ leq n ze złożonością czasową lepszą niż O (n ^ 3) ?
Odpowiedzi:
Istnieje dość praktyczne podejście, które działa w czasie , gdzie jest liczbą bitów w słowie procesora. Główną ideą jest to, że iterujesz kolejno elementy macierzy w kolejności rosnącej (arbitralnie zrywaj wiązania) i „włączaj je”. Rozważ moment, w którym włączony jest największy element potrójnego . Dla uproszczenia załóżmy, że wspomniany element to . Naturalne jest dodanie wartości potrójnej do odpowiedzi teraz, gdy ostatni element jest włączony. Musimy więc policzyć liczbę możliwych , takich jak isą już włączone (byłaby to liczba potrójnych, tutaj jest największym elementem, więc zostały całkowicie włączone). Tutaj możemy przyspieszyć naiwną implementację za pomocą optymalizacji bitów.
Szczegółowe informacje można znaleźć w następującej implementacji w C ++ 11, która powinna działać dla , (nie jest zbytnio zoptymalizowany, jednak nadal bije naiwne sumowanie dla o dużym marginesie przynajmniej na mojej maszynie).
// 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;
}
Jeśli zastanawiasz się nad wykorzystaniem oszukiwania bitów, możesz użyć metody czterech Rosjan do tego samego wyniku, uzyskując algorytm , który powinien być mniej praktyczny (ponieważ jest dość duży na większości nowoczesnych urządzeń) ale teoretycznie jest lepszy. Rzeczywiście, wybierzmy i zachowajmy każdy wiersz macierzy jako tablicę liczb całkowitych od do , gdzie -ta liczba w tablica odpowiada bitom rzędu od włącznie do wyłączne w-indeksacja. Możemy wstępnie obliczyć iloczyn skalarny co dwa takie bloki w czasie . Aktualizacja pozycji w macierzy jest szybka, ponieważ zmieniamy tylko jedną liczbę całkowitą. Aby znaleźć iloczyn skalarny wierszy i po prostu iteruj po tablicach odpowiadających tym wierszom, wyszukaj produkty skalarne odpowiednich bloków w tabeli i podsumuj uzyskane produkty.
Powyższy akapit zakłada, że operacje na liczbach całkowitych zajmują czas . Jest to dość powszechne założenie , ponieważ zwykle nie zmienia w rzeczywistości szybkości porównawczej algorytmów (na przykład, jeśli nie użyjemy tego założenia, metoda brutalnej siły faktycznie działa w czasie (tutaj mierzymy czas w operacjach bitowych) jeśli weźmy wartości całkowite o wartościach bezwzględnych co najmniej do dla niektórych stałych (a w przeciwnym razie możemy rozwiązać problem z tak mnożenia macierzy), jednak sugerowana powyżej metoda czterech Rosjan używaOperacje z numerami o rozmiarze w takim przypadku; więc wykonuje operacje bitowe , co jest nadal lepsze niż brutalna siła pomimo zmiany modelu).
Pytanie o istnienie podejścia jest jednak nadal interesujące.
Techniki (optymalizacja bitów i metoda czterech Rosjan) przedstawione w tej odpowiedzi nie są bynajmniej oryginalne i zostały tutaj przedstawione w celu uzupełnienia ekspozycji. Jednak znalezienie sposobu ich zastosowania nie było trywialne.
mat[i]
mat[j]
mat
które wydają się ważne. Rozumiem, jak można to zdefiniować, ale zastanawiam się, czy (mat[i] & mat[j]).count()
będzie działać zgodnie z oczekiwaniami z dowolnym kontenerem STL.
mat
- myślę, że musimy użyć std::vector<boost::dynamic_bitset<int64_t>>
.
mat
: tak, miałem na myśli standardowy zestaw bitów, ale boost::dynamic_bitset
w tym przypadku jest jeszcze lepszy, ponieważ jego rozmiar nie musi być stały w czasie kompilacji. Przeredaguje odpowiedź, aby dodać ten szczegół i wyjaśnić podejście czterech Rosjan.