Məsələ iki növ sorğunun emal edilməsini tələb edir: massivin bir elementinə qiymət mənimsətmək və [l; r] seqmentindəki elementlərin cəmini hesablamaq. Əgər yalnız massivin özünü saxlasaq, mənimsətmə vaxtda yerinə yetirilir, lakin hər cəm sorğusu əməliyyat tələb edir. Əgər prefiks cəmlərini saxlasaq, cəm vaxtda hesablanır, lakin hər mənimsətmə prefiks cəminin yenidən hesablanmasını tələb edir. Sorğuların sayı böyük olduqda hər iki yanaşma da çox yavaş olur.
Cəmlər üçün seqment ağacından istifadə edirik. 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ər iki əməliyyatı O(\log n) vaxtında yerinə yetirir. Cəm hesablanarkən seqmentin boş hissəsi -a — toplama üçün neytral elementə — uyğun gəlir.
Başlanğıcda massivin bütün elementləri sıfırdır, buna görə də ağacın bütün təpələrindəki cəmlər də sıfırdır. Ağac massivini sıfırlarla doldurmaq kifayətdir; ağacın ayrıca qurulmasına ehtiyac yoxdur.
Elementlərin qiymətləri 10^9-a çata bilər, buna görə seqmentdəki cəm 10^5 \cdot 10^9 = 10^{14}-ə çata bilər və int tipinə sığmır. Ağacdakı qiymətlər long long tipində saxlanılır.
Fenvik ağacı istifadə edən həlli nəzərdən keçirək. O, hər biri O(\log n) vaxtında yerinə yetirilən iki əməliyyatı dəstəkləyir:
a_i elementinə ədədinin əlavə edilməsi;
prefiks cəminin hesablanması sum(i) = a_0 + a_1 + \ldots + a_i.
Başlanğıcda massivin bütün elementləri sıfırdır, buna görə sıfırlarla doldurulmuş Fenvik ağacı artıq başlanğıc massivə uyğun gəlir. Sorğulardakı indekslər -dən -ə qədərdir; a_0 elementi istifadə olunmur və həmişə sıfırdır.
Mənimsətmə. Fenvik ağacı yalnız elementə ədəd əlavə edə bilər, lakin ona yeni qiymət mənimsədə bilməz. Lakin elementin cari qiymətini prefiks cəmləri vasitəsilə hesablamaq olar:
Buna görə a_i = x mənimsətməsi a_i-yə yeni və köhnə qiymətlər arasındakı fərqin əlavə edilməsi ilə əvəz olunur:
Seqment üzrə cəm. [l; r] seqmentindəki elementlərin cəmi iki prefiks cəminin fərqinə bərabərdir:
Elementlərin qiymətləri 10^9-a çata bilər, buna görə cəmlər 10^{14}-ə çata bilər və long long tipində saxlanılır.
Misal
Başlanğıcda massiv sıfırlardan ibarətdir. , və sorğularından sonra massiv (0, 2, 1, 2, 0) şəklini alır. Onun üçün seqment ağacı aşağıda göstərilmişdir. Hər təpədə onun seqmenti və bu seqmentdəki elementlərin cəmi göstərilir.
sorğusu yerinə yetirildikdə yalnız yarpağından kökə qədər olan yoldakı təpələr yenidən hesablanır: yarpağı qiymətini alır, təpəsi — 2 + 1 = 3 qiymətini, kökü isə — 3 + 2 = 5 qiymətini alır.
, , , və sorğuları yarpaqların qiymətlərini qaytarır: 0, 2, 1, 2, 0. sorğusunun seqmenti kökün seqmenti ilə üst-üstə düşür, buna görə cavab birbaşa kökdən götürülür və -ə bərabərdir (vurğulanmış təpə).
Misaldakı sorğuları Fenvik ağacı ilə simulyasiya edək, n = 5. Başlanğıcda massiv (0, 0, 0, 0, 0)-dır. Hər mənimsətmə sorğusu üçün cədvəldə elementin köhnə qiymətinin necə hesablandığı və hansı ədədin əlavə edildiyi göstərilir; hər cəm sorğusu üçün isə prefiks cəmlərinin fərqi göstərilir.
sorğu | hesablama | nəticə |
köhnə qiymət sum(2) - sum(1) = 0 - 0 = 0, əlavə edilir 2 - 0 = 2 | (0, 2, 0, 0, 0) | |
köhnə qiymət sum(3) - sum(2) = 2 - 2 = 0, əlavə edilir 1 - 0 = 1 | (0, 2, 1, 0, 0) | |
köhnə qiymət sum(4) - sum(3) = 3 - 3 = 0, əlavə edilir 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 |
Məsələn, sorğusundan əvvəl massiv (0, 2, 1, 2, 0)-dır, buna görə sum(4) = 0 + 2 + 1 + 2 = 5, sum(3) = 0 + 2 + 1 = 3, cavab isə 5 - 3 = 2-dir.
Misalda bütün mənimsətmələr sıfır elementlərə tətbiq olunur. Gəlin təkrar mənimsətmə zamanı nə baş verəcəyinə baxaq, məsələn sonda sorğusuna. Köhnə qiymət sum(3) - sum(2) = 3 - 2 = 1-dir, buna görə a_3-ə 5 - 1 = 4 əlavə olunur və element olur.
Alqoritmin tətbiqi
Seqment ağacı uzunluğu 4 \cdot MAX olan SegTree massivində saxlanılır, burada — massiv elementlərinin maksimum sayıdır. Seqmentdəki cəm int tipinin sərhədlərini aşa bilər, buna görə ağacdakı qiymətlər long long tipinə malikdir.
#define MAX 100010
long long SegTree[4 * MAX];Summa funksiyası [left; right] seqmentindəki elementlərin cəmini qaytarır. təpəsi [lpos; rpos] seqmentinə uyğun gəlir.
long long Summa(int v, int lpos, int rpos, int left, int right)
{Əgər sorğu seqmenti boşdursa (left > right), qaytarılır — toplama üçün neytral element.
if (left > right) return 0;Əgər sorğu seqmenti təpəsinin [lpos; rpos] seqmenti ilə üst-üstə düşürsə, orada saxlanılan cəm qaytarılır.
if ((left == lpos) && (right == rpos))
return SegTree[v];Əks halda seqmentin ortası tapılır mid = (lpos + rpos) / 2.
int mid = (lpos + rpos) / 2;Sorğu seqmenti iki hissəyə bölünür: [left; \min(right, mid)] sol övladın seqmentində yerləşir, [\max(left, mid + 1); right] isə sağ övladın seqmentində yerləşir. Hissələrdən biri boş ola bilər. Hər iki hissədəki cəmlər rekursiv şəkildə hesablanır və onların cəmi qaytarılır.
return Summa(2*v, lpos, mid, left, min(right, mid)) +
Summa(2*v+1, mid + 1, rpos, max(left, mid + 1), right);
}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əsinin seqmenti bir elementdən ibarətdirsə (lpos = rpos), onda mövqeyinə uyğun gələn yarpaqdır. Ona qiymətini yazın.
if (lpos == rpos)
SegTree[v] = val;
else
{Əks halda, seqmentin hansı yarısında mövqeyinin yerləşdiyi müəyyən edilir və rekursiv şəkildə sol və ya sağ övlada enilir.
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əm yenidən hesablanır. 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];
}
}Proqramın əsas hissəsi. Başlanğıcda massivin bütün elementləri sıfırdır, buna görə seqment ağacındakı bütün cəmlər də sıfırdır. SegTree massivini sıfırlarla doldurun; ağacın ayrıca qurulmasına ehtiyac yoxdur.
memset(SegTree, 0, sizeof(SegTree));Massivin ölçüsü və sorğuların sayı oxunur. Formatdakı \n simvolu sətir sonunu buraxır ki, növbəti oxunan simvol sorğu hərfi olsun.
scanf("%d %d\n", &n, &k); sorğu bir-bir emal edilir. Əvvəlcə sorğu hərfi oxunur. Əgər bu 'A'-dırsa, indeksli elementə qiyməti mənimsədilir. Əks halda [l; r] seqmentindəki cəm çap edilir. "%lld %lld\n" formatındakı \n simvolu sorğudan sonrakı sətir sonunu buraxır.
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));
}
}Alqoritmin tətbiqi — Fenvik ağacı
ölçülü Fenwick massivi Fenvik ağacını saxlayır. Qlobal massiv başlanğıcda sıfırlarla doldurulur ki, bu da başlanğıc sıfır massivinə uyğun gəlir.
#define MAX 100010
long long Fenwick[MAX];Summma0_i funksiyası sum(i) = a_0 + a_1 + \ldots + a_i prefiks cəmini qaytarı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 funksiyası a_i elementinə əlavə edir. Elementlərin indeksləri -dən -ə qədərdir, buna görə dövr i \le n şərti ilə işləyir.
// Fenwick[i] = Fenwick[i] + delta
void IncElement(long long i, long long delta)
{
for (; i <= n; i = (i | (i+1)))
Fenwick[i] += delta;
}Proqramın əsas hissəsi. Massivin ölçüsü və sorğuların sayı oxunur. Formatdakı \n simvolu sətir sonunu buraxır ki, növbəti oxunan simvol sorğu hərfi olsun.
scanf("%d %d\n",&n,&k);sorğu bir-bir emal edilir. Əvvəlcə sorğu hərfi oxunur.
for(j = 0; j < k; j++)
{
scanf("%c",&command);sorğusu. a_i elementinin cari qiyməti sum(i) - sum(i - 1)-ə bərabərdir. Ona qiymətini mənimsətmək üçün x - sum(i) + sum(i - 1) fərqi ilə əvəz olunur və IncElement funksiyası vasitəsilə a_i-yə əlavə edilir.
if (command == 'A')
{
scanf("%lld %lld\n",&i,&x);
x = x - Summma0_i(i) + Summma0_i(i-1);
IncElement(i,x);sorğusu. [l; r] seqmentindəki cəm sum(r) - sum(l - 1)-ə bərabərdir.
} else
{
scanf("%lld %lld\n",&l,&r);
printf("%lld\n",Summma0_i(r) - Summma0_i(l-1));
}
}