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

Материал из Олимпиадное программирование в УлГТУ
Перейти к навигации Перейти к поиску
Нет описания правки
Нет описания правки
 
(не показано 7 промежуточных версий этого же участника)
Строка 3: Строка 3:
:* [http://www.spoj.com/problems/DQUERY/ Количество различных элементов на отрезке]
:* [http://www.spoj.com/problems/DQUERY/ Количество различных элементов на отрезке]
:* Наиболее частый элемент на отрезке
:* Наиболее частый элемент на отрезке
:* Количество инверсий на отрезке
:* [http://codeforces.com/contest/220/problem/B Количество чисел x, встречающихся x раз на отрезке]
:* [http://codeforces.com/contest/220/problem/B Количество чисел x, встречающихся x раз на отрезке]
:* [http://www.codechef.com/problems/IITI15 Количество инверсий на отрезке]
* Элементы массива не меняются;
* Элементы массива не меняются;
* Ответ на запросы в оффлайне (запросы будут сортироваться, отвечать будем не в том порядке, в котором они даны).
* Ответ на запросы в оффлайне (запросы будут сортироваться, отвечать будем не в том порядке, в котором они даны).
Строка 11: Строка 11:
1. Мысленно делим массив на sqrt(N) блоков длины sqrt(N); для каждого запроса вычисляем номер блока, в который попадает его левый конец:
1. Мысленно делим массив на sqrt(N) блоков длины sqrt(N); для каждого запроса вычисляем номер блока, в который попадает его левый конец:
  struct Query {
  struct Query {
     int left, right, block, index;
     int l, r, block, index;
  } queries[100010];
  };
   
   
  int blockSize = sqrt(arraySize);  
  int blockSize = sqrt(a.size());
  for (int i = 0; i < queriesCount; i++) {
vector<Query> queries(queryCount);
     scanf("%d%d", &queries[i].left, &queries[i].right);
  for (int i = 0; i < queryCount; i++) {
     queries[i].block = queries[i].left / blockSize;
     int queryL, queryR;
     queries[i].index = i;
     cin >> queryL >> queryR;
   
     queries[i] = { queryL, queryR, queryL / blockSize, i });
  }
  }


2. Сортируем запросы сначала по возрастанию блока, затем по возрастанию правого конца:
2. Сортируем запросы сначала по возрастанию блока, затем по возрастанию правого конца:
  bool compare(Query &a, Query &b) {
  bool operator < (const Query &that) const {
     return tie(a.block, a.right) < tie(b.block, b.right);
     if (block != that.block)
        return block < that.block;
    return r < that.r;
  }
  }
   
   
  sort(queries, queries + queriesCount, compare);
  sort(queries.begin(), queries.end());


3. Заводим индексы l и r для начала и конца рабочего отрезка, рассчитываем ответ на рабочем отрезке:
3. Заводим индексы l и r для начала и конца рабочего отрезка, рассчитываем ответ на рабочем отрезке:
  int l = 0, r = 0, curAns = ... /* ответ для [0..0] */;
Counter counter;
  int l = 0, r = 0;
counter.add(a[l], 1);


4. Обрабатываем запросы в порядке сортировки, втупую двигая l и r куда нужно и обновляя ответ для рабочего отрезка:
4. Обрабатываем запросы в порядке сортировки, втупую двигая l и r куда нужно и обновляя ответ для рабочего отрезка:
  for (int i = 0; i < queriesCount; i++) {
vector<int> res(queries.size());
     while (l < queries[i].left) {
  for (auto &[queryL, queryR, block, index] : queries) {
         ... /* исключаем a[l], обновляем curAns */
     while (queryL < l)
        l++;
         counter.add(a[--l], 1);
    }
     while (l < queryL)
     while (l > queries[i].left) {
         counter.add(a[l++], -1);
        l--;
     while (queryR < r)
         ... /* добавляем a[l], обновляем curAns */
         counter.add(a[r--], -1);
    }
     while (r < queryR)
     while (r < queries[i].right) {
         counter.add(a[++r], 1);
        r++;
     res[index] = counter.getRes();
         ... /* добавляем a[r], обновляем curAns */
    }
     while (r > queries[i].right) {           
         ... /* исключаем a[r], обновляем curAns */
        r--;
     }
    ans[queries[i].index] = curAns;
  }
  }


== За сколько это работает ==
Обрати внимание: в некоторых задачах важен порядок операций: например, сначала расширяем рабочий отрезок, затем сужаем.
Предполагаем, что curAns обновляется за O(1).
 
== Сложность ==
Предполагаем, что ответ обновляется за O(1).


Всего обрабатывается sqrt(N) блоков. Внутри каждого блока:
Всего обрабатывается sqrt(N) блоков. Внутри каждого блока:
* l при каждом запросе движется влево/вправо не более чем на sqrt(N). Всего по всем блокам — не более Qsqrt(N) раз (Q — количество запросов);
* l при каждом запросе может сдвигаться влево или вправо, но каждый раз не более чем на sqrt(N). Всего по всем блокам — не более Qsqrt(N) раз (Q — количество запросов);
* r движется только вправо, не более N раз для блока. Всего по всем блокам — не более Nsqrt(N) раз.
* r может сдвигаться только вправо, не более N раз для блока. Всего по всем блокам — не более Nsqrt(N) раз.


Итого асимптотика '''(Q + N)sqrt(N)''' (плюс QlogQ на сортировку).
Итоговая асимптотика '''(Q + N)sqrt(N)''' (плюс QlogQ на сортировку запросов).


== Ссылки ==
== Ссылки ==
Строка 65: Строка 66:
* [http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9C%D0%BE neerc.ifmo.ru/wiki — Алгоритм Мо]
* [http://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9C%D0%BE neerc.ifmo.ru/wiki — Алгоритм Мо]
* [http://ipc.susu.ru/575.html ipc.susu.ru — Алгоритм Мо]
* [http://ipc.susu.ru/575.html ipc.susu.ru — Алгоритм Мо]
* [http://wiki.algocode.ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9C%D0%BE algocode.ru — Алгоритм Мо]
* [http://blog.anudeep2011.com/mos-algorithm/ blog.anudeep2011.com — Mo's algorithm]
* [http://blog.anudeep2011.com/mos-algorithm/ blog.anudeep2011.com — Mo's algorithm]
* [http://www.hackerearth.com/ru/practice/notes/mos-algorithm/ hackerearth.com — Mo's algorithm]
* [http://www.hackerearth.com/ru/practice/notes/mos-algorithm/ hackerearth.com — Mo's algorithm]
* [http://www.hackerrank.com/topics/mos-algorithm hackerrank.com — Mo's algorithm]
* [http://www.hackerrank.com/topics/mos-algorithm hackerrank.com — Mo's algorithm]
* [http://codeforces.com/blog/entry/72690 Codeforces — Mo's Algorithm (with and without update)]
* [https://codeforces.com/blog/entry/81716 Codeforces — Everything on Mo's Algorithm]
* [https://codeforces.com/blog/entry/83630 Codeforces — Два варианта написания алгоритма Мо и 3Д Мо]
Задачи:
Задачи:
* [http://codeforces.com/contest/86/problem/D Codeforces 86.D]
* [http://codeforces.com/contest/86/problem/D Codeforces 86.D]
* [http://codeforces.com/contest/220/problem/B Codeforces 220.B]
* [http://codeforces.com/contest/220/problem/B Codeforces 220.B]
* [http://www.spoj.com/problems/DQUERY SPOJ DQUERY]
* [http://www.spoj.com/problems/DQUERY SPOJ DQUERY]
* [http://www.codechef.com/problems/IITI1 CodeChef IITI1] (понадобится дерамида или сжатие координат + дерево Фенвика)

Текущая версия от 10:51, 23 августа 2026

Когда применяется

  • Тяжёлые запросы на отрезках:
  • Элементы массива не меняются;
  • Ответ на запросы в оффлайне (запросы будут сортироваться, отвечать будем не в том порядке, в котором они даны).

Что делаем

1. Мысленно делим массив на sqrt(N) блоков длины sqrt(N); для каждого запроса вычисляем номер блока, в который попадает его левый конец:

struct Query {
    int l, r, block, index;
};

int blockSize = sqrt(a.size());
vector<Query> queries(queryCount);
for (int i = 0; i < queryCount; i++) {
    int queryL, queryR;
    cin >> queryL >> queryR;
    
    queries[i] = { queryL, queryR, queryL / blockSize, i });
}

2. Сортируем запросы сначала по возрастанию блока, затем по возрастанию правого конца:

bool operator < (const Query &that) const {
    if (block != that.block)
        return block < that.block;
    return r < that.r;
}

sort(queries.begin(), queries.end());

3. Заводим индексы l и r для начала и конца рабочего отрезка, рассчитываем ответ на рабочем отрезке:

Counter counter;
int l = 0, r = 0;
counter.add(a[l], 1);

4. Обрабатываем запросы в порядке сортировки, втупую двигая l и r куда нужно и обновляя ответ для рабочего отрезка:

vector<int> res(queries.size());
for (auto &[queryL, queryR, block, index] : queries) {
    while (queryL < l)
        counter.add(a[--l], 1);
    while (l < queryL)
        counter.add(a[l++], -1);
    while (queryR < r)
        counter.add(a[r--], -1);
    while (r < queryR)
        counter.add(a[++r], 1);
    res[index] = counter.getRes();
}

Обрати внимание: в некоторых задачах важен порядок операций: например, сначала расширяем рабочий отрезок, затем сужаем.

Сложность

Предполагаем, что ответ обновляется за O(1).

Всего обрабатывается sqrt(N) блоков. Внутри каждого блока:

  • l при каждом запросе может сдвигаться влево или вправо, но каждый раз не более чем на sqrt(N). Всего по всем блокам — не более Qsqrt(N) раз (Q — количество запросов);
  • r может сдвигаться только вправо, не более N раз для блока. Всего по всем блокам — не более Nsqrt(N) раз.

Итоговая асимптотика (Q + N)sqrt(N) (плюс QlogQ на сортировку запросов).

Ссылки

Теория:

Задачи: