Bibble və Kapitan Snappy#4486

Bibble və Kapitan Snappy (Həll təhlili)

Məsələni açıqlayan bir xətti ağaç quracıq, bu ağac toplama əməliyyatını dəstəkləyir. Hər bir zirvədə (əsas segment ) əlavə olaraq dəyərini saxlayacağıq. Əgər left > 0 isə, bu zaman zirvədə gecikdirilmiş bir əməliyyat var: onun alt ağacının bütün zirvələrinə left, left + 1, ..., left + b - a dəyərləri təsis edilməlidir. Əvvəlcə, bütün zirvələrdə 0-a bərabərdir.

Dəyərləri ötürmə prosesinə nəzər salaq. Tutaq ki, sorğu q(x, y) hər iki alt ağacı qismən kəsir, yəni x \le m < y. Bu halda:

Əgər a \ne x~(b \ne y), bu halda dəyəri daha ötürülməlidir.

Əgər x \ge m + 1 (sorğu intervalı tamamilə sağ alt ağacda yerləşirsə), bu halda sol övlad üçün dəyəri 0 olaraq qalır.

Tutaq ki, sorğu q(x, y) [a_1, b_1], [a_2, b_2], ..., [a_k, b_k] əsas segmentləri ilə örtülür. Bu halda [a_i, b_i] segmenti üçün dəyərini

1~+~əvvəlki~[a_j,b_j]~segmentlərinin~uzunluqlarının~cəmi,~j < i~olduqda

müəyyən edirik.

Məsələn, ağac intervalında qurulubsa və sorğu q(2, 7) aşağıdakı əsas segmentləri örtürsə:

Nümunə

Növbəti testi nəzərdən keçirək:

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

Hər bir zirvənin yaxınında düzbucaqda intervalın sum resm halı yazılmışdır.

Alqoritmin realizasiyası

Dəmir ağacı saxlamaq üçün SegTree massivini elan edək.

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

BuildTree funksiyası xətti ağacı qurur.

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 funksiyası ilə arasındaki rəqəmlərin cəmini hesablayır.

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

Push funksiyası zirvəsindən onun oğullarına dəyərini ötürür. Oğullar üçün yenidən hesablanır.

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 funksiyası [left; right] intervallını, from, from + 1, from + 2, ... ardıcıllığı ilə doldurur.

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;

Əgər sorğu intervalı [mid + 1; rpos] sağ alt ağacda tamamilə yerləşirsə, sağ oğula dəyərini dəqiq dəyişiklik etmədən ötürürük (bu halda 0 olaraq təyin edilir).

  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 funksiyası [left; right] intervalında rəqəmlərin cəmini qaytarır.

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);
}

Proqramın əsas hissəsi. Giriş rəqəmlər massivini oxuyuruq.

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

Xətti ağacı qururuq.

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

sorğularını ardıcıl olaraq işləyirik.

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);
}