المپدیا

دانش‌نامه‌ی المپیاد کامپیوتر ایران

ابزار کاربر

ابزار سایت


آموزش:الگوریتم‌های تکمیلی:درخت فنویک

فرض کنید آرایه $a$ به اندازه $n$ که در ابتدا همه مقادیر آن برابر $0$ هستند به ما داده شده است، می خواهیم اعمال زیر را روی این آرایه انجام دهیم:

  • به درایه $i$-ام، $x$ واحد اضافه کن.
  • مجموع اعداد درایه $i$-ام تا درایه $j$-ام را به دست بیاور.

یک راه حل ساده برای این مسئله با استفاده از آرایه، به این صورت است که دستور $1$ را در $O(1)$ و دستور $2$ را در $O(n)$ انجام می دهد. پس اگر $m$ دستور داشته باشیم، در بدترین حالت (زمانی که همه دستورها از نوع دوم باشند)، به زمان $O(n.m)$ احتیاج داریم. با استفاده از داده ساختار درخت فنویک (یا درخت اندیس داده شده دودویی) می توان هر دستور را در $O(logn)$ و در نتیجه کل دستورها را در $O(m.logn)$ انجام داد. به طور کلی این اعمال توسط درخت بازه‌ها هم در این زمان انجام می شوند، اما مزیت درخت فنویک در کم بودن حافظه(دقیقاً به اندازه $n$)، کوتاه بودن کد و همچنین سهولت پیاده سازی آن در ابعاد بیشتر است.

ساختار

ایده اصلی این درخت این است که هر عدد طبیعی را می توان به صورت جمع تعدادی از توان‌های $2$ نوشت. به همین ترتیب مجموع یک بازه را می توان به صورت جمع تعدادی زیربازه بدون اشتراک از آن بازه نوشت.

فرض کنید $r$ کوچکترین عددی باشد که بیت $r$-ام عدد $x$ برابر $1$ باشد. تعریف می کنیم:

  • $tree[x] = sum[x-2^r+1...x]$
  • $tree[0] = 0$

(که $a[i]$ = عدد $i$ام ورودی و $c[i]$ = جمع اولین عدد تا $i$امین عدد آرایه و $tree[i]$ = جمع اعدادی که در بازه ی نسبت داده شده به عدد $i$ در ارایه قرار دارند.)

پیدا کردن آخرین بیت $1$

اگر عدد $p$ را به صورت مکمل-۲ نمایش دهیم، ارزش آخرین بیت یک آن برابر است با: $p & -p$

بدست آوردن آرایه $c$ از روی آرایه $tree$

برای بدست آوردن $c[x]$ ابتدا متغیر $sum$ را برابر $tree[x]$ قرار می دهیم، سپس بیت آخر $x$ را حذف می کنیم و $sum$ را با$tree[x]$ جمع می کنیم، همین کار را تکرار می کنیم تا $x$ برابر $0$ شود.

مثال: $c[15] = sum[15] + sum[14] + sum[12] + sum[8]$

int c(int x){
	int sum = 0;
	for(; x > 0; x -= x & (-x))
		sum += tree[x];
	return sum;
}

به طور کلی می توانیم بگوییم که در درخت، راس $x$ پدر تمام رئوسی است که با حذف بیت آخرشان به $x$ تبدیل می شوند.

بنابر این می توان $c(x)$ را در زمان $O(logn)$ محاسبه کرد، در نتیجه می توان $sum[i...j]$ و همچنین $a[i]$ را نیز در $O(logn)$ بدست آورد.$(a[i] = sum[i...i])$

به روز کردن مقادیر $tree$

بعد از این که به درایه $i$-ام، مقدار $val$ را اضافه می کنیم، باید به همه $tree[j]$ هایی که $i$ در بازه $j$ قرار می گیرد، مقدار $val$ را اضافه کنیم. برای این کار ابتدا به $tree[i]$، این مقدار را اضافه میکنیم، سپس کوچکترین بیت $1$ در $i$ را به آن اضافه می کنیم و تا زمانی که $i<=n$ باشد این کار را ادامه می دهیم.

void update(int i, int val){
	for(; i<=n; i += i & (-i))
		tree[i] += val;
}

به این ترتیب می توانیم به هر دو دستور در $O(logn)$ جواب دهیم.


ابزار صفحه