Data una matrice simmetrica reale , esiste un algoritmo che calcola la somma nel complesso 1 \ leq i <j <k \ leq n con complessità temporale migliore di O (n ^ 3) ?
Data una matrice simmetrica reale , esiste un algoritmo che calcola la somma nel complesso 1 \ leq i <j <k \ leq n con complessità temporale migliore di O (n ^ 3) ?
Risposte:
Esiste un approccio abbastanza pratico che funziona nel tempo , dove è il numero di bit nella parola del processore. L'idea principale è che si ripetano gli elementi della matrice uno alla volta in ordine crescente (rompendo i legami arbitrariamente) e "accendendoli". Considera il momento in cui l'elemento più grande di qualche tripla è attivato. Per semplicità, supponiamo che l'elemento detto sia . È naturale aggiungere il valore del triplo alla risposta ora, quando l'ultimo elemento è acceso. Quindi dobbiamo contare il numero di possibili tale che esono già accesi (che sarebbe il numero di triple, qui è l'elemento più grande, quindi sono stati completamente accesi proprio ora). Qui possiamo accelerare l' implementazione ingenua utilizzando l'ottimizzazione dei bit.
Per i dettagli è possibile fare riferimento alla seguente implementazione in C ++ 11 che dovrebbe funzionare per , (non è molto ottimizzato; tuttavia, batte comunque la somma ingenua per con un ampio margine almeno sulla mia macchina).
// 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;
}
Se consideri l'utilizzo di trucchi per l'ottimizzazione dei bit, puoi usare quattro metodi russi per lo stesso risultato qui, producendo un algoritmo , che dovrebbe essere meno pratico (perché è piuttosto grande sulla maggior parte dell'hardware moderno) ma è teoricamente migliore. Effettivamente, scegliamo e manteniamo ogni riga della matrice come una matrice di interi da a , dove l' -numero in l'array corrisponde ai bit della riga che vanno da inclusivo a esclusivo in-indexation. Possiamo precalcolare i prodotti scalari di ogni due blocchi di questo tipo nel tempo . L'aggiornamento di una posizione nella matrice è veloce perché stiamo cambiando solo un numero intero. Per trovare il prodotto scalare delle righe e basta scorrere le matrici corrispondenti a quelle righe, cercare i prodotti scalari dei blocchi corrispondenti nella tabella e riassumere i prodotti ottenuti.
Il paragrafo precedente presuppone che le operazioni con numeri interi richiedano il tempo . È un'ipotesi abbastanza comune , perché di solito non cambia effettivamente la velocità comparativa degli algoritmi (ad esempio, se non usiamo tale ipotesi, il metodo della forza bruta funziona effettivamente nel tempo (qui misuriamo il tempo in operazioni a bit) se accetta valori interi con valori assoluti almeno fino a per alcune costanti (e altrimenti possiamo risolvere il problema con moltiplicazioni di matrice comunque); tuttavia il metodo dei quattro russi suggerito sopra utilizza operazioni con numeri di dimensione in quel caso; quindi esegue operazioni di bit , che è comunque migliore della forza bruta nonostante il cambiamento del modello).
La domanda sull'esistenza dell'approccio è comunque interessante.
Le tecniche (ottimizzazioni di bit e metodo dei quattro russi) presentate in questa risposta non sono affatto originali e sono presentate qui per completezza dell'esposizione. Tuttavia, trovare un modo per applicarli non era banale.
mat[i]
mat[j]
mat
quale sembra essere importante. Capisco come potrebbe essere definito, ma mi chiedo se (mat[i] & mat[j]).count()
funzionerebbe come desiderato con qualsiasi contenitore STL.
mat
- immagino che dobbiamo usare std::vector<boost::dynamic_bitset<int64_t>>
.
mat
: sì, avevo in mente un bitset standard, ma boost::dynamic_bitset
in questo caso è ancora meglio, perché le sue dimensioni non devono essere costanti in fase di compilazione. Modifica la risposta per aggiungere questo dettaglio e chiarire l'approccio dei quattro russi.