Біббл і Капітан Снеппі#4486

Біббл і Капітан Снеппі (Розбір)

Побудуємо дерево відрізків, яке підтримує операцію підсумовування. У кожній вершині (фундаментальному відрізку ) додатково будемо зберігати значення . Якщо left > 0, то у вершині є відкладена операція: всім вершинам її піддерева слід призначити значення left, left + 1, ..., left + b - a. Спочатку у всіх вершинах дорівнює нулю.

Розглянемо процедуру просування значень. Нехай запит q(x, y) частково перетинає обидва піддерева, тобто x \le m < y. У цьому випадку:

Якщо a \ne x~(b \ne y), то значення слід просувати далі.

Якщо x \ge m + 1 (відрізок запиту повністю лежить у правому піддереві), то значення для лівого сина залишається рівним .

Нехай запит q(x, y) покривається набором фундаментальних відрізків [a_1, b_1], [a_2, b_2], ..., [a_k, b_k]. Тоді значення для відрізка [a_i, b_i] встановлюємо рівним

1~+~сума~довжин~всіх~попередніх~відрізків~[a_j,b_j],~для яких~j < i

Наприклад, нехай дерево побудоване на відрізку , а запит q(2, 7) покриває такі фундаментальні відрізки:

Приклад

Розглянемо наступний тест:

5
5 3 2 4 5
2
2 2 5
1 1 3

Біля кожної вершини в прямокутнику записана сума на відрізку.

Реалізація алгоритму

Оголосимо масив SegTree для зберігання дерева відрізків.

struct node
{
  long long summa;
  int left;
};
vector<node> SegTree;

Функція BuildTree будує дерево відрізків.

void BuildTree(int v, int lpos, int rpos)
{
  if (lpos == rpos)
    SegTree[v].sum = a[lpos];
  else
  {
    int mid = (lpos + rpos) / 2;
    BuildTree(2 * v, lpos, mid);
    BuildTree(2 * v + 1, mid + 1, rpos);
    SegTree[v].sum = SegTree[2 * v].sum + SegTree[2 * v + 1].sum;
  }
}

Функція Sum обчислює суму чисел від до .

long long Sum(int a, int b)
{
  return 1LL * (a + b) * (b - a + 1) / 2;
}

Функція Push виконує просування значення з вершини в її синів. Виконується перерахунок суми в синах.

void Push(int v, int lpos, int mid, int rpos)
{
  if (SegTree[v].left)
  {
     SegTree[2 * v].left = SegTree[v].left;
     SegTree[2 * v].sum = Sum(SegTree[2 * v].left, 
                              SegTree[2 * v].left + mid - lpos);

     SegTree[2 * v + 1].left = SegTree[v].left + mid - lpos + 1;
     SegTree[2 * v + 1].sum = Sum(SegTree[2 * v + 1].left, 
                        SegTree[2 * v + 1].left + rpos - mid - 1);
     SegTree[v].left = 0;
  }
}

Функція Update заповнює проміжок [left; right] послідовністю значень from, from + 1, from + 2, ... .

void Update(int v, int lpos, int rpos, int left, int right, int from)
{
  if (left > right) return;
  if ((lpos == left) && (rpos == right))
  {
    SegTree[v].left = from;
    SegTree[v].sum = Sum(from, from + right - left);
    return;
  }

  int mid = (lpos + rpos) / 2;
  int midval = mid - left + 1;

Якщо відрізок запиту [left; right] повністю лежить в правому піддереві [mid + 1; rpos], то передаємо правому сину значення без змін (при цьому встановлюємо рівним нулю).

  if (midval < 0) midval = 0;
  Push(v, lpos, mid, rpos);
  Update(2 * v, lpos, mid, left, min(mid, right), from);
  Update(2 * v + 1, mid + 1, rpos, max(left, mid + 1), 
                             right, from + midval);
  SegTree[v].sum = SegTree[2 * v].sum + SegTree[2 * v + 1].sum;
}

Функція Summa повертає суму чисел на відрізку [left; right].

long long Summa(int v, int lpos, int rpos, int Left, int Right)
{
  if (Left > Right) return 0;
  if ((lpos == Left) && (rpos == Right)) return SegTree[v].sum;
    
  int mid = (lpos + rpos) / 2;
  Push(v, lpos, mid, rpos);

  return Summa(2 * v, lpos, mid, Left, min(mid, Right)) +
         Summa(2 * v + 1, mid + 1, rpos, max(Left, mid + 1), Right);
}

Основна частина програми. Читаємо вхідний масив чисел.

scanf("%d", &n);
a.resize(n + 1);
for(i = 1; i <= n; ++i)
  scanf("%d", &a[i]);

Будуємо дерево відрізків.

SegTree.resize(4 * n);
BuildTree(1, 1, n);

Послідовно обробляємо запитів.

scanf("%d", &q);
for(i = 0; i < q; ++i)
{
  scanf("%d %d %d", &type, &l, &r);
  if(type == 1)
    printf("%lld\n", Summa(1,1,n,l,r));
  else
    Update(1,1,n,l,r,1);
}