electicode
ГлавнаяКурсыРесурсыЗадачиНациональная олимпиадаСоревнованияТаблица лидеров
...
Задачи/Массивы и Запросы/Разбор

Разбор: Массивы и Запросы

Обозначения и определения

Обозначим:

  • nnn = количество элементов в массиве aaa
  • bbb = окончательный сконструированный массив размером 2n−12^n - 12n−1
  • S(l,r)S(l, r)S(l,r) = сумма элементов bl+bl+1+…+brb_l + b_{l+1} + \ldots + b_rbl​+bl+1​+
  • Для любой позиции ppp (индексация с 1), мы можем представить её в двоичном виде
  • MOD=109+7\text{MOD} = 10^9 + 7MOD=109+7

Ключевое наблюдение: Значение в позиции ppp (индексация с 1) в массиве bbb определяется позицией наименьшего установленного бита (конечных нулей) в ppp. В частности, если позиция ppp имеет kkk конечных нулей в её двоичном представлении, то bp=ak+1b_p = a_{k+1}b.

Подзадача 1: Все элементы равны (ai=ca_i = cai​=c для всех iii)

Редакция: Когда все элементы равны некоторой константе ccc, каждое положение в массиве bbb имеет значение ccc. Следовательно:

S(l,r)=c⋅(r−l+1) mod MODS(l, r) = c \cdot (r - l + 1) \bmod \text{MOD}S(l,r)=c⋅(r−l+1)modMOD

Временная сложность: O(1)O(1)O(1) за запрос
Пространственная сложность: O(1)O(1)O(1)

Подзадача 2: Запросы, где l=1,r=2k−1l = 1, r = 2^k - 1l=1,r=2k−1

Редакция: Эти запросы запрашивают сумму всего массива, построенного после kkk раундов. Мы можем использовать рекурсию:

  • После раунда 1: сумма = a1a_1a1​
  • После раунда iii: сумма = 2⋅sumi−1+ai2 \cdot \text{sum}_{i-1} + a_i2⋅sumi−1​+a

Это потому, что b:=b+[ai]+bb := b + [a_i] + bb:=b+[ai​]+b, так что новая сумма в два раза больше старой суммы плюс aia_iai​.

Временная сложность: O(n)O(n)O(n) за запрос
Пространственная сложность: O(1)O(1)O(1)

Подзадача 3: n≤20n \leq 20n≤20

Редакция: Для небольших nnn мы можем фактически явно построить массив bbb, так как ∣b∣=2n−1≤220−1≈106|b| = 2^n - 1 \leq 2^{20} - 1 \approx 10^6∣b∣=2n−1≤2. Затем мы можем вычислить префиксные суммы и ответить на каждый запрос за .

Временная сложность: O(2n+q)O(2^n + q)O(2n+q)
Пространственная сложность: O(2n)O(2^n)O(2n)

Подзадача 4: Точечные запросы (l=rl = rl=r)

Редакция: Для точечного запроса в позиции ppp нам нужно выяснить, какой элемент массива aaa находится в позиции ppp. Ключевое понимание заключается в том, что bp=ak+1b_p = a_{k+1}bp​=ak, где — это количество конечных нулей в двоичном представлении (эквивалентно, позиция наименьшего установленного бита, индексируемая с 0 справа).

Алгоритм:

Пример:

  • Позиция 1 (двоичный: 001): 0 конечных нулей → a1a_1a1​
  • Позиция 2 (двоичный: 010): 1 конечный нуль → a2a_2a2​
  • Позиция 3 (двоичный: 011): 0 конечных нулей → a1a_1a1​
  • Позиция 4 (двоичный: 100): 2 конечных нуля →

Временная сложность: O(1)O(1)O(1) за запрос
Пространственная сложность: O(n)O(n)O(n)

Подзадача 5: Полное решение

Редакция: Ключевое понимание заключается в том, чтобы эффективно вычислить префиксные суммы без построения массива. Для позиции nnn (индексация с 1) мы можем вычислить PrefixSum(n)\text{PrefixSum}(n)PrefixSum(n), анализируя двоичное представление nnn.

Математическое понимание: Вклад каждого aia_iai​ в префиксную сумму можно вычислить, исследуя биты nnn:

PrefixSum(n)=∑i=0k−1⌊n2i⌋⋅ai−⌊n2i⌋⋅ai−1\text{PrefixSum}(n) = \sum_{i=0}^{k-1} \left\lfloor \frac{n}{2^i} \right\rfloor \cdot a_i - \left\lfloor \frac{n}{2^i} \right\rfloor \cdot a_{i-1}PrefixSum(n)=∑i=0k−1​⌊

где a−1=0a_{-1} = 0a−1​=0.

Алгоритм работает следующим образом:

  1. Инициализировать ans=0\text{ans} = 0ans=0, prev=0\text{prev} = 0prev=0
  2. Для каждой битовой позиции iii от 0 до n−1n-1n−1 (пока n>0n > 0n>):

Чтобы ответить на запрос (l,r)(l, r)(l,r): вычислить PrefixSum(r)−PrefixSum(l−1)\text{PrefixSum}(r) - \text{PrefixSum}(l-1)PrefixSum(r)−PrefixSum(l−1).

Почему это работает: Каждый элемент aia_iai​ появляется в позициях, где количество конечных нулей равно iii. Количество таких позиций в первых nnn позициях следует шаблону, основанному на двоичном представлении nnn. Член ⌊n/2i⌋\lfloor n/2^i \rfloor⌊n/2 подсчитывает вхождения, а вычитание вклада предыдущего элемента учитывает переход между уровнями.

Временная сложность: O(n)O(n)O(n) за запрос
Пространственная сложность: O(n)O(n)O(n)

← Вернуться к задаче
…
+
br​
p
​
=
ak+1​
i​
20
−
1≈
106
O(1)O(1)O(1)
+
1
​
kkk
ppp
a3a_3
a3​
2i
n
​
⌋
⋅
ai​−
⌊2in​⌋⋅
ai−1​
0
  • Вычесть вклад предыдущего элемента: ans−=n⋅prev\text{ans} -= n \cdot \text{prev}ans−=n⋅prev
  • Добавить вклад текущего элемента: ans+=n⋅ai\text{ans} += n \cdot a_ians+=n⋅ai​
  • Обновить: prev=ai\text{prev} = a_iprev=ai​, n=⌊n/2⌋n = \lfloor n/2 \rfloorn=⌊n/2⌋
i
⌋
function getValue(p):
    k = __builtin_ctzll(p)  // подсчет конечных нулей
    return a[k]
#include <bits/stdc++.h>
using namespace std;

const int64_t MOD = 1e9 + 7;
int64_t a[99], n;

int64_t mul(int64_t x, int64_t y) {
    x = (x % MOD + MOD) % MOD;
    y = (y % MOD + MOD) % MOD;
    return (x * y) % MOD;
}

int64_t add(int64_t x, int64_t y) {
    return (MOD + (x + y) % MOD) % MOD;
}

// Вычислить префиксную сумму для позиций от 1 до pos
int64_t prefixSum(int64_t pos) {
    int64_t ans = 0, prev = 0;
    for (int i = 0; i < n && pos > 0; i++) {
        ans = add(ans, mul(-pos, prev));
        ans = add(ans, mul(pos, a[i]));
        prev = a[i];
        pos /= 2;
    }
    return ans;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    
    int q;
    cin >> q;
    while (q--) {
        int64_t l, r;
        cin >> l >> r;
        cout << add(prefixSum(r), -prefixSum(l - 1)) << '\n';
    }
    
    return 0;
}