Dima və massiv#2941

Dima və massiv (Həll təhlili)

Məsələ iki tip əməliyyatın emalını tələb edir:

Bizə hər iki əməliyyatı sürətlə yerinə yetirən bir verilənlər strukturu lazımdır. Belə bir struktur cəmlər üçün seqment ağacıdır. Onun hər bir təpəsi öz seqmentinin elementlərinin cəmini saxlayır, daxili təpədəki qiymət isə onun iki övladının qiymətlərinin cəminə bərabərdir.

Seqment ağacı həm seqmentdəki cəmi hesablamağa, həm də massivin elementini O(\log n) vaxtında dəyişməyə imkan verir.

Misal

Nümunədəki sorğuları simulyasiya edək. Əvvəlcə massiv (1, 2, 3)-dir.

Seqment ağacının necə dəyişdiyinə baxaq. Solda ilkin massiv üçün ağac, sağda isə a_3 = 2 mənimsəməsindən sonrakı ağac göstərilib. Hər təpədə onun seqmenti və bu seqmentdəki elementlərin cəmi göstərilib; dəyişən təpələr işıqlandırılıb.

Hər iki sorğunun seqmenti kökün seqmenti ilə üst-üstə düşür, buna görə cavab birbaşa kökdən götürülür: mənimsəmədən əvvəl , sonra isə . a_3 = 2 mənimsəməsi zamanı yalnız yarpağından kökə qədər olan yoldakı təpələr yenidən hesablanır: yarpağı qiymətini alır, kök isə 3 + 2 = 5 qiymətini alır.

Alqoritmin realizasiyası

Seqment ağacını uzunluğu 4 \cdot MAX olan SegTree vektorunda saxlayaq, burada MAX = 5 \cdot 10^5 massiv elementlərinin maksimum sayıdır. Ağacın hər bir təpəsi öz seqmentinin elementlərinin cəmini saxlayır.

#define MAX 500000
vector<int> SegTree(4*MAX,0);

BuildTree funksiyası massivindən seqment ağacını qurur. O, cari təpənin nömrəsi və bu təpəyə uyğun gələn seqmentin və sərhədlərini qəbul edir.

void BuildTree(vector<int>&a, int v, int lpos, int rpos)
{

Əgər təpəsinin seqmenti bir elementdən ibarətdirsə (lpos = rpos), onda ağacın yarpağıdır. Ona qiymətini yazırıq.

  if (lpos == rpos) SegTree[v] = a[lpos];
  else
  {

Əks halda, seqmentin ortasını mid = (lpos + rpos) / 2 tapırıq.

    int mid = (lpos + rpos) / 2;

[lpos; rpos] seqmenti iki alt-seqmentə bölünür: [lpos; mid] və [mid + 1; rpos]. Sol övlad və sağ övlad 2v + 1 üçün ağacı rekursiv şəkildə quraq.

    BuildTree(a, 2*v, lpos, mid);
    BuildTree(a, 2*v+1, mid + 1, rpos);

təpəsindəki qiymət onun övladlarının qiymətlərinin cəminə bərabərdir.

    SegTree[v] = SegTree[2*v] + SegTree[2*v+1];
  }
}

Update funksiyası indeksli elementə qiymətini mənimsədir. təpəsi [lpos; rpos] seqmentinə uyğun gəlir.

void Update(int v, int lpos, int rpos, int pos, int val)
{

Əgər təpəsi yarpaqdırsa (lpos = rpos), o, mövqeyinə uyğun gəlir. Ona qiymətini yazırıq.

  if (lpos == rpos) SegTree[v] = val;
  else
  {

Əks halda, seqmentin hansı yarısında mövqeyinin olduğunu müəyyən edirik və rekursiv şəkildə sol və ya sağ övlada enirik.

    int mid = (lpos + rpos) / 2;
    if (pos <= mid)
      Update (2*v, lpos, mid, pos, val);
    else
      Update (2*v+1, mid+1, rpos, pos, val);

Rekursiyadan qayıtdıqdan sonra təpəsindəki cəmi yenidən hesablayırıq. Bu şəkildə yarpaqdan kökə qədər olan yoldakı bütün təpələr yenilənir.

    SegTree[v] = SegTree[2*v] + SegTree[2*v+1];
  }
}

Summa funksiyası [left; right] seqmentindəki elementlərin cəmini qaytarır. təpəsi [lpos; rpos] seqmentinə uyğun gəlir. Əgər sorğu seqmenti boşdursa (left > right), qaytarılır — toplama üçün neytral element.

int Summa(int v, int lpos, int rpos, int left, int right)
{
  if (left > right) return 0;

Əgər sorğu seqmenti təpənin seqmenti ilə üst-üstə düşürsə, cavab SegTree[v]-də saxlanılır.

  if ((left == lpos) && (right == rpos))
    return SegTree[v];

Əks halda, seqmentin ortasını mid = (lpos + rpos) / 2 tapırıq.

  int mid = (lpos + rpos) / 2;

Sorğu seqmentini iki hissəyə bölək: [left; \min(right, mid)] sol övladın seqmentində, [\max(left, mid + 1); right] isə sağ övladın seqmentində yerləşir. Hissələrdən biri boş ola bilər. Hər iki övlad üçün nəticələrin cəmini qaytarırıq.

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

Proqramın əsas hissəsi. Massivin ölçüsünü -i, sorğuların sayını -nu və massivin elementlərini vektoruna, -ci indeksdən başlayaraq oxuyuruq. scanf("\n") çağırışı massivdən sonrakı sətir sonu simvolunu ötürür ki, növbəti oxunan simvol əməliyyat işarəsi olsun.

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

v[1 \ldots n] elementlərindən seqment ağacını quraq.

BuildTree(v,1,1,n);

sorğusunu bir-bir emal edək. Əvvəlcə əməliyyat simvolunu oxuyuruq; "%c " formatındakı boşluq ondan sonrakı boşluğu ötürür. Əgər = '=' olarsa, indeksli elementə qiymətini mənimsədirik. Əks halda, seqmentindəki cəmi hesablayıb çap edirik. "%d %d\n" formatındakı \n simvolu sətir sonunu ötürür ki, növbəti dəfə -yə oxunan növbəti əməliyyatın işarəsi olsun.

for(j = 0; j < q; j++)
{
  scanf("%c ",&c);
  if (c == '=')
  {
    scanf("%d %d\n",&i,&d);
    Update(1,1,n,i,d);
  } else
  {
    scanf("%d %d\n",&f,&t);
    printf("%d\n",Summa(1,1,n,f,t));
  }
}