Чи існує підкубний алгоритм для наступної проблеми?


11

Дано симетричну дійсну n×n матрицю A=(aij) , чи існує алгоритм, який обчислює суму по всьому 1 \ leq i <j <k \ leq n з часовою складністю кращою за O (n ^ 3) ?1i<j<knO(n3)

i,j,kmax(aij,aik,ajk)
1i<j<knO(n3)

3
Зауважте, що це як мінімум настільки ж важко, як підрахунок кількості трикутників у заданому графіку. Якщо ваша вхідна матриця кодує графік таким чином, що "0" вказує край, а "1" вказує на відсутній край, то max(aij,aik,ajk)=0 якщо і тільки якщо є - трикутник, утворений вузлами i , j і k , інакше він дорівнює 1 .
Юкка Суомела

1
Я думаю, що єдині відомі субкубічні алгоритми підрахунку трикутників засновані на швидкому множенні матриці? Тут може бути складно застосувати ці методи в цій проблемі. Крім того, якщо ви шукаєте щось практичне, нічого, що базується на швидкому множенні матриці, не буде корисним.
Юкка Суомела

Відповіді:


3

Існує цілком практичний підхід, який працює в час, де - кількість біт у слові процесора. Основна ідея полягає в тому, що ви перебираєте елементи матриці по черзі в порядку зростання (розриваєте зв’язки довільно) і "включаєте їх". Розглянемо момент, коли вмикається найбільший елемент деякої потрійної . Для простоти припустимо, що згаданий елемент є . Природно додавати значення трійки до відповіді зараз, коли останній елемент увімкнено. Таким чином, ми повинні порахувати кількість можливих таких, що іO(n3/w)waij,aik,ajkaijkaikajk a i j O ( n )вже ввімкнено (це було б число трійки, тут є найбільшим елементом, тому вони були повністю включені лише зараз). Тут ми можемо прискорити наївну реалізацію за допомогою бітової оптимізації.aijO(n)

Для детальної інформації можна звернутися до наступної реалізації в C ++ 11, яка повинна працювати для , (він не дуже оптимізований; проте, він все ще б'є наївне підсумовування за за великим запасом, принаймні, на моїй машині).n5000|aij|109n=5000

// 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(n3/logn)wblog2nnb02b1iibmin(n,(i+1)b)0-індексація. Ми можемо заздалегідь обчислити скалярні добутки кожного двох таких блоків за час . Оновлення позиції в матриці відбувається швидко, оскільки ми змінюємо лише одне ціле число. Щоб знайти скалярний добуток рядків та просто перебирайте масиви, відповідні цим рядкам, шукайте скалярні добутки відповідних блоків у таблиці та підсумовуйте отримані продукти.O(22bb)ij

Вищенаведений абзац передбачає, що операції з цілими числами займають час . Це досить поширене припущення , оскільки воно фактично фактично не змінює порівняльну швидкість алгоритмів (наприклад, якщо ми не використовуємо це припущення, метод грубої сили фактично працює в час (тут ми вимірюємо час у бітових операціях), якщо приймає цілі значення з абсолютними значеннями принаймні до для деякої постійної (інакше ми можемо вирішити задачу з матричне множення в будь-якому випадку); проте, запропонований вище чотири росіяни використовує методnO(1)O ( n 3 log n ) a i j n ε ε > 0 O ( n ε )O(n3logn)aijnεε>0O(nε)O(n3/logn)O ( log n ) O ( n 3 ) операції з номерами розміру у цьому випадку; тому він робить бітові операції, що все ж краще, ніж груба сила, незважаючи на зміну моделі).O(logn)O(n3)

Питання про існування підходу все ще цікаве.O(n3ε)

Методи (бітова оптимізація та метод чотирьох росіян), представлені у цій відповіді, аж ніяк не оригінальні та представлені тут для повноти експозиції. Однак пошук способу їх застосування не був банальним.


По-перше, ваша пропозиція справді виявляється корисною в практичному плані, я можу просто спробувати це у моєму випадку використання. Дякую! По-друге, обчислювальна складність ваших алгоритмів все ще для будь-якого числового типу з фіксованою шириною. Чи можете ви детальніше розглянути питання про підхід ? Я не розумію, як ми могли б знайти скалярний добуток і швидше, ніж (що було б потрібно, якщо ми отримаємо доступ до всіх їх елементів). O ( n 3 / log n ) O ( n )O(n3)O(n3/logn)mat[i]mat[j]O(n)
користувач89217

Крім того, ваш код не визначає, matщо здається важливим. Я розумію, як це можна було б визначити, але мені цікаво, чи (mat[i] & mat[j]).count()працював би за бажанням будь-який контейнер STL
користувач89217

1
Щодо mat- я думаю, ми повинні використовувати std::vector<boost::dynamic_bitset<int64_t>>.
user89217

Щодо mat: так, я мав на увазі стандартний біт, але boost::dynamic_bitsetв цьому випадку це навіть краще, оскільки його розмір не повинен бути постійним за часом компіляції. Відредагуйте відповідь, щоб додати цю деталь та уточнити підхід чотирьох росіян.
Кабан-5,

1
Чудово, мені це виглядає солідно. Незначне значення: оскільки трансдіхотомна модель передбачає, що ми можемо виконувати операції над машинними словами в , немає необхідності в перерахунку будь-яких скалярних продуктів. Насправді модель передбачає, що , тому принаймні настільки ж хороший, як . І, як ви кажете, попередньо розраховувати скалярні продукти не має жодного практичного сенсу (пошук масиву буде повільніше, ніж двійковий опис). w log 2 n O ( n 3 / w ) O ( n 3 / log n )O(1)wlog2nO(n3/w)O(n3/logn)
user89217
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.