Алгоритм Мо: различия между версиями

Материал из Algocode wiki
Перейти к: навигация, поиск
м
Строка 1: Строка 1:
Пусть есть задача на массиве, которую мы решаем без запросов обновления, в offline. Запросы могут быть любой
+
Теперь обсудим еще одно решение задачи прибавления и суммы на отрезке. Допустим, что все запросы даны нам заранее и запросов обновления нет. Для каждого запроса закинем его в блок, где лежит его левая граница:
степени ужасности, если мы умеем на них отвечать структурой данных, которая хранит информацию об отрезке $[l, r]$ и
 
поддерживает следующие операции:  
 
* add_left
 
* add_right
 
* del_left
 
* del_right
 
* query
 
  
Которые меняют границы отрезка и внутреннюю структуру данных, а также позволяют сделать запрос. Все запросы мы пересортируем. Теперь они будут отсортированы по паре $(\lfloor \sqrt{l} \rfloor, r)$. Отвечать на запрос мы будем таким образом --- с помощщью реализованных операций подстроим необходимые нам границы отрезка, после чего вызовем $query$.
+
<syntaxhighlight lang="C++">
 +
s_dec[l / s].a.push_back({l, r});
 +
</syntaxhighlight>
  
Оценим время выполнения. Заметим, что левые границы разбиваются по блокам, а внутри запросов одного блока
+
Затем внутри каждого блока отсортируем запросы по правой границе. Теперь давайте пройдемся по каждому блоку, поддерживая текущий отрезок, на который мы знаем ответ. И если текущий отрезок равен отрезку из запроса запоминать его, как ответ.  
правые границы отсортированы. При переходе от запроса к запросу внутри блока, левая граница делает $O(\sqrt n)$ сдвигов.
 
Правая же граница для запросов одного блока суммарно сделает $O(n)$ сдвигов. Так как число блоков $O(\sqrt n)$, то и
 
суммарное время работы $O(\sqrt n)$.
 
  
{{Автор|Константин Амеличев|kik0s}}
+
<syntaxhighlight lang="C++">
 +
struct q {
 +
    int l, r, index;
 +
};
 +
 
 +
struct block {
 +
    vector<q> a;
 +
};
 +
 
 +
for(int i = 0; i < q; i++) {
 +
    s_dec[l / s].a.push_back({l, r, i});
 +
}
 +
 
 +
for(int i = 0; i < s; i++) {
 +
    sort(all(s_dec[i].a), comp);
 +
}
 +
 
 +
for(int i = 0; i < s; i++) {
 +
    int l = i * s, r = i * s;
 +
    int ans = 0;
 +
    for(int j = 0; j < s_dec[i].a.size(); j++) {
 +
        int tl = s_dec[i].a[j].l, tr = s_dec[i].a[j].r;
 +
        while (r > tr){
 +
            r—;
 +
            ans -= a[r];
 +
        }
 +
        while(r < tr) {
 +
            ans += a[r];
 +
            r++;
 +
        }
 +
        while(l > tl) {
 +
            l--;
 +
            ans += a[l];
 +
        }
 +
        while(l < tl) {
 +
            ans -= a[l];
 +
            l++;
 +
        }
 +
        answ[s_dec[i].a[j].index] = ans;
 +
}
 +
</syntaxhighlight>
 +
 
 +
Подумаем над асимптотикой данного алгоритма, для каждого запроса левая граница пройдет не более, чем длину блока, но при этом правая граница идет только вперед, а следовательно для одного блока пройдет не более, чем длину массива, то есть суммарно алгоритм работает за $O(N\sqrt(M) + M\sqrt(N))$.
 +
 
 +
Алгоритм Мо может помочь нам в ответе на гораздо более сложные вопросы - k-ая статистика на отрезке, медиана отрезка, наиболее встречающийся элемент на отрезке и так далее.
 +
 
 +
{{Автор|Глеб Лобанов|glebodin}}
 
[[Категория:Структуры данных]]
 
[[Категория:Структуры данных]]
 
[[Категория:Конспекты]]
 
[[Категория:Конспекты]]

Версия 16:09, 22 октября 2019

Теперь обсудим еще одно решение задачи прибавления и суммы на отрезке. Допустим, что все запросы даны нам заранее и запросов обновления нет. Для каждого запроса закинем его в блок, где лежит его левая граница:

s_dec[l / s].a.push_back({l, r});

Затем внутри каждого блока отсортируем запросы по правой границе. Теперь давайте пройдемся по каждому блоку, поддерживая текущий отрезок, на который мы знаем ответ. И если текущий отрезок равен отрезку из запроса запоминать его, как ответ.

struct q {
    int l, r, index;
};

struct block {
    vector<q> a;
};

for(int i = 0; i < q; i++) {
    s_dec[l / s].a.push_back({l, r, i});
}

for(int i = 0; i < s; i++) {
    sort(all(s_dec[i].a), comp);
}

for(int i = 0; i < s; i++) {
    int l = i * s, r = i * s;
    int ans = 0;
    for(int j = 0; j < s_dec[i].a.size(); j++) {
        int tl = s_dec[i].a[j].l, tr = s_dec[i].a[j].r;
        while (r > tr){
            r;
            ans -= a[r];
        }
        while(r < tr) {
            ans += a[r];
            r++;
        }
        while(l > tl) {
            l--;
            ans += a[l];
        }
        while(l < tl) {
            ans -= a[l];
            l++;
        }
        answ[s_dec[i].a[j].index] = ans;
}

Подумаем над асимптотикой данного алгоритма, для каждого запроса левая граница пройдет не более, чем длину блока, но при этом правая граница идет только вперед, а следовательно для одного блока пройдет не более, чем длину массива, то есть суммарно алгоритм работает за $O(N\sqrt(M) + M\sqrt(N))$.

Алгоритм Мо может помочь нам в ответе на гораздо более сложные вопросы - k-ая статистика на отрезке, медиана отрезка, наиболее встречающийся элемент на отрезке и так далее.



Автор конспекта: Глеб Лобанов

По всем вопросам пишите в telegram @glebodin