Дано симетричну дійсну матрицю , чи існує алгоритм, який обчислює суму по всьому 1 \ leq i <j <k \ leq n з часовою складністю кращою за O (n ^ 3) ?1≤i<j<k≤nO(n3)
Дано симетричну дійсну матрицю , чи існує алгоритм, який обчислює суму по всьому 1 \ leq i <j <k \ leq n з часовою складністю кращою за O (n ^ 3) ?1≤i<j<k≤nO(n3)
Відповіді:
Існує цілком практичний підхід, який працює в час, де - кількість біт у слові процесора. Основна ідея полягає в тому, що ви перебираєте елементи матриці по черзі в порядку зростання (розриваєте зв’язки довільно) і "включаєте їх". Розглянемо момент, коли вмикається найбільший елемент деякої потрійної . Для простоти припустимо, що згаданий елемент є . Природно додавати значення трійки до відповіді зараз, коли останній елемент увімкнено. Таким чином, ми повинні порахувати кількість можливих таких, що і a i j O ( n )вже ввімкнено (це було б число трійки, тут є найбільшим елементом, тому вони були повністю включені лише зараз). Тут ми можемо прискорити наївну реалізацію за допомогою бітової оптимізації.
Для детальної інформації можна звернутися до наступної реалізації в C ++ 11, яка повинна працювати для , (він не дуже оптимізований; проте, він все ще б'є наївне підсумовування за за великим запасом, принаймні, на моїй машині).
// 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;
}
Якщо ви розглядаєте можливість використання обману бітових оптимізацій, ви можете використовувати тут чотири росіяни для того ж результату, даючи алгоритм , який повинен бути менш практичним (адже досить великий на більшості сучасних апаратних засобів) але теоретично краще. Дійсно, виберемо і збережемо кожен рядок матриці як масив цілих чисел від до , де -ме число в масив відповідає бітам рядка, що становить від включно до виключно в-індексація. Ми можемо заздалегідь обчислити скалярні добутки кожного двох таких блоків за час . Оновлення позиції в матриці відбувається швидко, оскільки ми змінюємо лише одне ціле число. Щоб знайти скалярний добуток рядків та просто перебирайте масиви, відповідні цим рядкам, шукайте скалярні добутки відповідних блоків у таблиці та підсумовуйте отримані продукти.
Вищенаведений абзац передбачає, що операції з цілими числами займають час . Це досить поширене припущення , оскільки воно фактично фактично не змінює порівняльну швидкість алгоритмів (наприклад, якщо ми не використовуємо це припущення, метод грубої сили фактично працює в час (тут ми вимірюємо час у бітових операціях), якщо приймає цілі значення з абсолютними значеннями принаймні до для деякої постійної (інакше ми можемо вирішити задачу з матричне множення в будь-якому випадку); проте, запропонований вище чотири росіяни використовує методO ( n 3 log n ) a i j n ε ε > 0 O ( n ε )O ( log n ) O ( n 3 ) операції з номерами розміру у цьому випадку; тому він робить бітові операції, що все ж краще, ніж груба сила, незважаючи на зміну моделі).
Питання про існування підходу все ще цікаве.
Методи (бітова оптимізація та метод чотирьох росіян), представлені у цій відповіді, аж ніяк не оригінальні та представлені тут для повноти експозиції. Однак пошук способу їх застосування не був банальним.
mat[i]
mat[j]
mat
що здається важливим. Я розумію, як це можна було б визначити, але мені цікаво, чи (mat[i] & mat[j]).count()
працював би за бажанням будь-який контейнер STL
mat
- я думаю, ми повинні використовувати std::vector<boost::dynamic_bitset<int64_t>>
.
mat
: так, я мав на увазі стандартний біт, але boost::dynamic_bitset
в цьому випадку це навіть краще, оскільки його розмір не повинен бути постійним за часом компіляції. Відредагуйте відповідь, щоб додати цю деталь та уточнити підхід чотирьох росіян.