কনটেন্টে যান

Segment tree ও Fenwick tree — কোন অংশের উত্তর নিচ্ছি

অধ্যায়ের সূচি · সহজ ব্যাখ্যা ও বাস্তব ব্যবহার · অনুশীলন ও সমাধান

দুই structure-ই array-র কিছু অংশের উত্তর আগে থেকে রাখে। Query-তে দরকারি অংশগুলো নিই; update-এ শুধু বদলানো element-কে ধারণ করা অংশগুলো ঠিক করি। পাশাপাশি পড়ো: Segment tree, Fenwick tree।

Segment tree — ছোট range থেকে বড় range

Array [2,5,1,4,9,3]। প্রতিটি node-এ [বাম index..ডান index] এবং সেই range-এর sum লেখা। এখানে দুই প্রান্তই included।

flowchart TD
  root["[0..5] · sum 24"] --> l["[0..2] · sum 8"]
  root --> r["[3..5] · sum 16"]
  l --> ll["[0..1] · sum 7"]
  l --> v2["[2..2] · 1"]
  r --> rl["[3..4] · sum 13"]
  r --> v5["[5..5] · 3"]
  ll --> v0["[0..0] · 2"]
  ll --> v1["[1..1] · 5"]
  rl --> v3["[3..3] · 4"]
  rl --> v4["[4..4] · 9"]

Leaf একটি array element রাখে। Parent তার দুই child-এর sum রাখে। এই ছয়-element tree-তে 11টি node; সাধারণভাবে এভাবে তৈরি tree-তে 2n−1টি node থাকে, যদিও implementation-এর allocated array বড় হতে পারে।

Query [1,4] — কোন node নেব

চাই 5+1+4+9 = 19। একটি node-এর range পুরো query-র মধ্যে থাকলে তার জমা sum সরাসরি নিই। পুরো বাইরে থাকলে বাদ দিই। আংশিক মিললে children-এ নামি।

flowchart TD
  root["[0..5] · আংশিক, ভাগ করো"] --> l["[0..2] · আংশিক"]
  root --> r["[3..5] · আংশিক"]
  l --> ll["[0..1] · আংশিক"]
  l --> v2["[2..2] · নাও 1"]
  ll --> v0["[0..0] · বাদ"]
  ll --> v1["[1..1] · নাও 5"]
  r --> rl["[3..4] · নাও 13"]
  r --> v5["[5..5] · বাদ"]
নেওয়া range কোন value ঢুকল জমা sum
[1,1] 5 5
[2,2] 1 1
[3,4] 4, 9 13
সব মিলিয়ে [1,4] কোনো gap বা overlap নেই 19

[3,4] পুরোটা নিতে পেরেছি, তাই তার দুই leaf-এ নামিনি। মোট 11টি node-এর মধ্যে 9টিতে গিয়েছি, সরাসরি উত্তর নিয়েছি 3টি থেকে। বড় tree-তে query-র দুই boundary ধরে প্রতি level-এ সীমিত সংখ্যক node ভাঙে; তাই query time O(log n)।

Point update — a[2] = 6

আগে a[2]=1 ছিল। এটি বদলালে কেবল সেই leaf থেকে root পর্যন্ত node-গুলোর sum বদলায়।

ধাপ Range পুরোনো মান নতুন মান
Leaf [2,2] 1 6
Parent [0,2] 8 7+6 = 13
Root [0,5] 24 13+16 = 29
flowchart TD
  root["[0..5] = 29 · বদলেছে"] --> l["[0..2] = 13 · বদলেছে"]
  root --> r["[3..5] = 16"]
  l --> ll["[0..1] = 7"]
  l --> v2["[2..2] = 6 · বদলেছে"]
  r --> rl["[3..4] = 13"]
  r --> v5["[5..5] = 3"]
  ll --> v0["[0..0] = 2"]
  ll --> v1["[1..1] = 5"]
  rl --> v3["[3..3] = 4"]
  rl --> v4["[4..4] = 9"]

এখন একই query(1,4) নেয় [1,1] + [2,2] + [3,4]: উত্তর 5+6+13 = 24।

Fenwick — প্রতিটি index একটি block

এবার index 1 থেকে শুরু। lowbit(i) = i & -i; binary-তে সবচেয়ে ডানের set bit-এর মান। tree[i] রাখে [i−lowbit(i)+1, i] range-এর sum।

n = 16-এর সব block দেখো
i lowbit(i) Covered range Element সংখ্যা
1 1 [1,1] 1
2 2 [1,2] 2
3 1 [3,3] 1
4 4 [1,4] 4
5 1 [5,5] 1
6 2 [5,6] 2
7 1 [7,7] 1
8 8 [1,8] 8
9 1 [9,9] 1
10 2 [9,10] 2
11 1 [11,11] 1
12 4 [9,12] 4
13 1 [13,13] 1
14 2 [13,14] 2
15 1 [15,15] 1
16 16 [1,16] 16

Odd index শুধু নিজের element রাখে। Power-of-two index নিজের আগের পুরো prefix রাখে। বড় index মানেই বড় block নয়: 13-এর block এক element, 12-এর block চার element।

Fenwick query — prefix_sum(13)

বর্তমান tree[i] নিই, তারপর i -= lowbit(i) করি। এতে binary-র সবচেয়ে ডানের set bit মুছে যায়।

flowchart LR
  a["13 · নাও [13..13]"] -->|−1| b["12 · নাও [9..12]"]
  b -->|−4| c["8 · নাও [1..8]"]
  c -->|−8| done["0 · শেষ"]
i Binary নেওয়া block পরের i
13 1101 [13,13] 13−1 = 12
12 1100 [9,12] 12−4 = 8
8 1000 [1,8] 8−8 = 0

এই তিন block যোগ করলে [1,8] + [9,12] + [13,13]—পুরো [1,13], কোনো overlap নেই। 13-এর binary-তে তিনটি set bit, তাই তিনটি block নিয়েছি।

Fenwick update — add(5, +10)

Element 5-কে 10 বাড়ালে সেটিকে ধারণ করা সব block-এর sum 10 বাড়াতে হবে। বর্তমান tree[i] বদলাই, তারপর i += lowbit(i) করি। এখানে n=16।

flowchart LR
  a["5 · [5..5]"] -->|+1| b["6 · [5..6]"]
  b -->|+2| c["8 · [1..8]"]
  c -->|+8| d["16 · [1..16]"]
  d -->|+16| stop["32 > n · শেষ"]
i Binary কোন entry বাড়ে পরের i
5 0101 tree[5] += 10 6
6 0110 tree[6] += 10 8
8 1000 tree[8] += 10 16
16 10000 tree[16] += 10 32; থামি

প্রতিটি visited block-এ index 5 আছে। Update-এর পথ বড় block-এর দিকে যায়; prefix query-র পথ overlap ছাড়া আগের block-এর দিকে যায়। দুটোরই সময় O(log n)।

একই data, আলাদা storage

চার element [2,5,1,4]; এই তুলনায় দুই structure-তেই বাইরের index 1 থেকে ধরছি। Segment tree-এর সব node:

flowchart TD
  all["[1..4] · sum 12"] --> l["[1..2] · sum 7"]
  all --> r["[3..4] · sum 5"]
  l --> a["[1..1] · 2"]
  l --> b["[2..2] · 5"]
  r --> c["[3..3] · 1"]
  r --> d["[4..4] · 4"]

Fenwick storage মাত্র চারটি entry: tree[1]=2, tree[2]=7, tree[3]=1, tree[4]=12। Segment tree-এর মতো প্রতিটি অর্ধের node এখানে নেই; আছে lowbit-নির্ধারিত block।

প্রশ্ন Segment tree Fenwick sum tree
[2,4]-এর sum [2,2] + [3,4] = 5+5 prefix(4)−prefix(1) = 12−2
Element 3-এ +5 Leaf, [3,4], [1,4] বদলায় tree[3], tree[4] বদলায়
Range min Combine-এ min ব্যবহার করা যায় সাধারণ prefix subtraction পদ্ধতি চলে না

নিজে করো: update-এর পরে এই দুই structure দিয়ে আবার [2,4]-এর sum বের করো। দুই উত্তরই 15 হওয়া উচিত।