Bir aralıktaki toplam#4255

Bir aralıktaki toplam (Editoryal)

Problem, iki tür sorguyu işlemeyi gerektiriyor: bir dizi elemanına bir değer atama ve [l; r] aralığındaki elemanların toplamını hesaplama. Eğer yalnızca dizinin kendisini saklarsak, bir atama sürer, ancak her toplam sorgusu işlem gerektirir. Önek toplamlarını saklarsak, bir toplam sürede hesaplanır, ancak her atama önek toplamının yeniden hesaplanmasını gerektirir. Sorgu sayısı büyük olduğunda, her iki yaklaşım da çok yavaş kalır.

Toplamlar için bir segment ağacı kullanıyoruz. Ağacın her bir düğümü kendi aralığındaki elemanların toplamını saklar ve bir iç düğümdeki değer, iki çocuğunun değerlerinin toplamına eşittir. Segment ağacı her iki işlemi de O(\log n) sürede gerçekleştirir. Bir toplam hesaplanırken, aralığın boş kısmı 'a karşılık gelir — toplama işlemi için nötr eleman.

Başlangıçta tüm dizi elemanları sıfırdır, bu yüzden ağacın tüm düğümlerindeki toplamlar da sıfırdır. Ağaç dizisini sıfırlarla doldurmak yeterlidir; ağacın ayrıca inşa edilmesine gerek yoktur.

Elemanların değerleri 10^9'a ulaşabilir, bu yüzden bir aralıktaki toplam 10^5 \cdot 10^9 = 10^{14}'e ulaşabilir ve int türüne sığmaz. Ağaçtaki değerler long long türünde saklanır.

Şimdi Fenwick ağacı kullanan bir çözümü ele alalım. Her biri O(\log n) sürede gerçekleştirilen iki işlemi destekler:

Başlangıçta tüm dizi elemanları sıfırdır, bu yüzden sıfırlarla doldurulmuş bir Fenwick ağacı zaten başlangıç dizisine karşılık gelir. Sorgulardaki indeksler 'den 'ye kadar değişir; a_0 elemanı kullanılmaz ve her zaman sıfırdır.

Atama. Bir Fenwick ağacı yalnızca bir elemana bir sayı ekleyebilir, ancak ona yeni bir değer atayamaz. Ancak bir elemanın mevcut değeri önek toplamları aracılığıyla hesaplanabilir:

a_i = sum(i) - sum(i - 1)

Bu yüzden, a_i = x ataması, a_i'ye yeni ve eski değerler arasındaki farkı eklemekle değiştirilir:

delta = x - (sum(i) - sum(i - 1)) = x - sum(i) + sum(i - 1)

Aralık üzerinde toplam. [l; r] aralığındaki elemanların toplamı, iki önek toplamının farkına eşittir:

a_l + a_{l+1} + \ldots + a_r = sum(r) - sum(l - 1)

Elemanların değerleri 10^9'a ulaşabilir, bu yüzden toplamlar 10^{14}'e ulaşabilir ve long long türünde saklanır.

Örnek

Başlangıçta dizi sıfırlardan oluşur. , ve sorgularından sonra, dizi (0, 2, 1, 2, 0) haline gelir. Bunun için segment ağacı aşağıda gösterilmiştir. Her düğüm kendi aralığını ve o aralıktaki elemanların toplamını gösterir.

sorgusu yürütüldüğünde, yalnızca yaprağından köke giden yol üzerindeki düğümler yeniden hesaplanır: yaprağı değerini alır, düğümü — 2 + 1 = 3 değerini, ve kökü — 3 + 2 = 5 değerini alır.

, , , ve sorguları yaprakların değerlerini döndürür: 0, 2, 1, 2, 0. sorgusunun aralığı kökün aralığıyla aynıdır, bu yüzden cevap doğrudan kökten alınır ve 'e eşittir (vurgulanmış düğüm).

Örnekteki sorguları Fenwick ağacı kullanarak simüle edelim, n = 5. Başlangıçta dizi (0, 0, 0, 0, 0)'dır. Her atama sorgusu için, tablo elemanın eski değerinin nasıl hesaplandığını ve hangi sayının eklendiğini gösterir; her toplam sorgusu için, önek toplamlarının farkını gösterir.

sorgu

hesaplama

sonuç

eski değer sum(2) - sum(1) = 0 - 0 = 0, eklenen 2 - 0 = 2

(0, 2, 0, 0, 0)

eski değer sum(3) - sum(2) = 2 - 2 = 0, eklenen 1 - 0 = 1

(0, 2, 1, 0, 0)

eski değer sum(4) - sum(3) = 3 - 3 = 0, eklenen 2 - 0 = 2

(0, 2, 1, 2, 0)

sum(1) - sum(0) = 0 - 0

sum(2) - sum(1) = 2 - 0

sum(3) - sum(2) = 3 - 2

sum(4) - sum(3) = 5 - 3

sum(5) - sum(4) = 5 - 5

sum(5) - sum(0) = 5 - 0

Örneğin, sorgusundan önce dizi (0, 2, 1, 2, 0)'dır, bu yüzden sum(4) = 0 + 2 + 1 + 2 = 5, sum(3) = 0 + 2 + 1 = 3, ve cevap 5 - 3 = 2'dir.

Örnekte, tüm atamalar sıfır elemanlara uygulanmıştır. Tekrarlanan bir atama ile ne olacağını görelim, örneğin sonunda . Eski değer sum(3) - sum(2) = 3 - 2 = 1'dir, bu yüzden a_3'e 5 - 1 = 4 eklenir ve eleman olur.

Algoritma uygulaması

Segment ağacı, uzunluğu 4 \cdot MAX olan SegTree dizisinde saklanır, burada dizi elemanlarının maksimum sayısıdır. Bir aralıktaki toplam int türünün sınırlarını aşabilir, bu yüzden ağaçtaki değerler long long türündedir.

#define MAX 100010
long long SegTree[4 * MAX];

Summa fonksiyonu, [left; right] aralığındaki elemanların toplamını döndürür. düğümü [lpos; rpos] aralığına karşılık gelir.

long long Summa(int v, int lpos, int rpos, int left, int right)
{

Sorgu aralığı boşsa (left > right), döndür — toplama işlemi için nötr eleman.

  if (left > right) return 0;

Sorgu aralığı düğümünün [lpos; rpos] aralığıyla aynıysa, onda saklanan toplamı döndür.

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

Aksi takdirde, aralığın ortasını bul: mid = (lpos + rpos) / 2.

  int mid = (lpos + rpos) / 2;

Sorgu aralığını iki parçaya böl: [left; \min(right, mid)] sol çocuğun aralığında, [\max(left, mid + 1); right] sağ çocuğun aralığında yer alır. Parçalardan biri boş olabilir. Her iki parçadaki toplamları özyinelemeli olarak hesapla ve toplamlarını döndür.

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

update fonksiyonu, indeksli elemana değerini atar. düğümü [lpos; rpos] aralığına karşılık gelir.

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

düğümünün aralığı tek bir elemandan oluşuyorsa (lpos = rpos), o zaman , konumuna karşılık gelen yapraktır. İçine değerini yaz.

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

Aksi takdirde, aralığın hangi yarısının konumunu içerdiğini belirle ve özyinelemeli olarak sol veya sağ çocuğa in.

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

Özyinelemeden döndükten sonra, düğümündeki toplamı yeniden hesapla. Bu şekilde, yapraktan köke giden yol üzerindeki tüm düğümler güncellenir.

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

Programın ana bölümü. Başlangıçta tüm dizi elemanları sıfırdır, bu yüzden segment ağacındaki tüm toplamlar da sıfırdır. SegTree dizisini sıfırlarla doldur; ağacın ayrıca inşa edilmesine gerek yoktur.

memset(SegTree, 0, sizeof(SegTree));

Dizinin boyutu 'i ve sorgu sayısı 'yı oku. Formattaki \n karakteri satır sonunu atlar, böylece okunacak bir sonraki karakter sorgu harfi olur.

scanf("%d %d\n", &n, &k);

sorguyu birer birer işle. İlk önce sorgu harfi 'ı oku. Eğer 'A' ise, indeksli elemana değerini ata. Aksi takdirde, [l; r] aralığındaki toplamı yazdır. "%lld %lld\n" formatındaki \n karakteri sorgudan sonraki satır sonunu atlar.

for (j = 0; j < k; j++)
{
  scanf("%c", &command);
  if (command == 'A')
  {
    scanf("%lld %lld\n", &i, &x);
    update(1, 1, n, i, x);
  }
  else
  {
    scanf("%lld %lld\n", &l, &r);
    printf("%lld\n", Summa(1, 1, n, l, r));
  }
}

Algoritma uygulaması — Fenwick ağacı

boyutundaki Fenwick dizisi, Fenwick ağacını saklar. Global bir dizi başlangıçta sıfırlarla doldurulur, bu da başlangıç sıfır dizisine karşılık gelir.

#define MAX 100010
long long Fenwick[MAX];

Summma0_i fonksiyonu, sum(i) = a_0 + a_1 + \ldots + a_i önek toplamını döndürür.

// Fenwick[0] + Fenwick[1] + ... + Fenwick[i]
long long Summma0_i(long long i)
{
  long long result = 0;
  for (; i >= 0; i = (i & (i + 1)) - 1)
    result += Fenwick[i];
  return result;
}

IncElement fonksiyonu, a_i elemanına ekler. Elemanların indeksleri 'den 'ye kadar değişir, bu yüzden döngü i \le n olduğu sürece çalışır.

// Fenwick[i] = Fenwick[i] + delta
void IncElement(long long i, long long delta)
{
  for (; i <= n; i = (i | (i+1)))
    Fenwick[i] += delta;
}

Programın ana bölümü. Dizinin boyutu 'i ve sorgu sayısı 'yı oku. Formattaki \n karakteri satır sonunu atlar, böylece okunacak bir sonraki karakter sorgu harfi olur.

scanf("%d %d\n",&n,&k);

sorguyu birer birer işle. İlk önce sorgu harfi 'ı oku.

for(j = 0; j < k; j++)
{
  scanf("%c",&command);

sorgusu. a_i elemanının mevcut değeri sum(i) - sum(i - 1)'e eşittir. Ona değerini atamak için, 'i x - sum(i) + sum(i - 1) farkıyla değiştir ve bunu IncElement fonksiyonunu kullanarak a_i'ye ekle.

  if (command == 'A')
  {
    scanf("%lld %lld\n",&i,&x);
    x = x - Summma0_i(i) + Summma0_i(i-1);
    IncElement(i,x);

sorgusu. [l; r] aralığındaki toplam sum(r) - sum(l - 1)'e eşittir.

  } else
  {
    scanf("%lld %lld\n",&l,&r);
    printf("%lld\n",Summma0_i(r) - Summma0_i(l-1));
  }
}