070 — Range Update Query¶
069-এ দুটো দাগ দিয়ে range update শিখলে। এবার সেটা একটা পূর্ণ pipeline-এ বাঁধব: প্রথমে অনেকগুলো range update, তারপর difference থেকে prefix চালিয়ে final array, আর শেষে সেই final array-র উপর 067-068-এর range query। অর্থাৎ এই note-এ Level 5-এর প্রথম তিন idea একসাথে হাত মেলায়। ধীরে পড়ো — ক্রমটাই আসল।
Source¶
- Original source: Classic exercise (difference array + prefix query pipeline)
- Platform: Classic exercise — https://usaco.guide/silver/more-prefix-sums
- Topic: Math-based programming fundamentals → Level 5: Prefix, Difference, Contribution
- Difficulty: Medium
- Pattern: difference array (update) + prefix sum (query)
- Basic idea inherited from: 069 — Difference Array
1. Problem in simple words¶
একটা array a (শুরুতে সব 0, দৈর্ঘ্য n)। দুই ধরনের কাজ আসবে:
- Update: "index
lথেকেrপর্যন্ত সবার সাথেxযোগ করো।" - Query: "index
qlথেকেqrপর্যন্ত sum কত?"
তবে একটা শর্ত: সব update আগে আসে, তারপর সব query। (মাঝখানে মেশে না।) তোমার কাজ — সব update দ্রুত প্রয়োগ করে চূড়ান্ত array বানানো, তারপর প্রতিটা query-র sum দ্রুত (O(1)) দেওয়া।
মানে দুটো কৌশল জোড়া লাগানো: update-এর জন্য difference array (069), query-র জন্য prefix sum (067-068)।
2. What is the math idea?¶
দুটো একে অপরের উল্টো অপারেশন পরপর সাজানো:
পর্যায় 1 (update): difference array — diff[l] += x; diff[r+1] -= x
পর্যায় 2 (build) : diff-এর prefix sum → চূড়ান্ত array a
পর্যায় 3 (query) : a-র prefix sum P → range sum = P[qr+1] - P[ql]
লক্ষ করো prefix sum দুবার এসেছে — একবার difference থেকে আসল array ফেরাতে, আরেকবার সেই array-র উপর fast query-র জন্য। মূল গণিত সেই দুই জোড়া বিপরীত অপারেশন: difference ↔ prefix (069), এবং range-sum ↔ prefix-difference (068)।
3. Which basic idea does this inherit from?¶
মূলত 069 (Difference Array) থেকে — সেখানকার update pipeline-টা এখানে পর্যায় 1 ও 2। তার উপর 067-068-এর range query পর্যায় 3-এ যোগ হয়।
069 ছিল "update করে final array পাও"। 070 হলো "তারপর সেই array-তে query-ও করো"। তাই 070 = 069 + 068 — তিনটা idea-র সম্মিলন। এই কারণেই এটা Level 5-এর মাঝামাঝি একটা সুন্দর mile-marker: এক problem-এ সব ঝালাই হয়ে যায়।
4. Real-life analogy¶
ভাবো একটা দোকানের stock register। সকালে নানা supplier মাল দিয়ে যায় — "shelf 1 থেকে 3-এ প্রতিটায় 10টা করে যোগ করো", "shelf 2 থেকে 4-এ 5টা করে"। এগুলো সব update, আর তুমি ছোট্ট স্লিপে দুই প্রান্তে দাগ দিয়ে রাখো (069-এর সাইনবোর্ড)।
দুপুরে সব মাল গুছিয়ে register-এ লিখে ফেলো (prefix চালিয়ে final stock)। বিকেলে manager আসে নানা প্রশ্ন নিয়ে — "shelf 1 থেকে 3-এ মোট কতটা মাল?" এগুলো query, আর তুমি running-total কলাম থেকে দুটো সংখ্যা বিয়োগ করে চটপট উত্তর দাও (068)।
সকালের কাজ (update) আর বিকেলের প্রশ্ন (query) আলাদা — মাঝে একবার গুছিয়ে নাও। এই "আগে সব বদল, তারপর সব প্রশ্ন" ক্রমটাই pipeline-এর প্রাণ।
5. Visual explanation¶
n = 5। Updates: (0, 2, 3), (1, 4, 2)। তারপর query (1, 3)।
পর্যায় 1 — difference array (দুই প্রান্তে দাগ):
শুরু: diff = [0, 0, 0, 0, 0, 0]
(0, 2, +3): diff = [3, 0, 0, -3, 0, 0]
(1, 4, +2): diff = [3, 2, 0, -3, 0, -2]
পর্যায় 2 — prefix চালিয়ে final array:
running: 3 → 3+2=5 → 5+0=5 → 5-3=2 → 2+0=2
a = [ 3, 5, 5, 2, 2 ]
index 0 1 2 3 4
পর্যায় 3 — a-র prefix P, তারপর range query:
P = [0, 3, 8, 13, 15, 17]
sum(1..3) = P[4] - P[1] = 15 - 3 = 12 (5 + 5 + 2 = 12 ✓)
তিন পর্যায় তিন রঙে ভাবো: দাগ দেওয়া → আসল array ফেরানো → query-র জন্য আবার prefix। প্রতিটা পর্যায়ই আসলে prefix/difference-এর চেনা মুখ, শুধু পরপর সাজানো।
6. Example input and output¶
n = 5
updates = [(0, 2, 3), (1, 4, 2)]
final a = [3, 5, 5, 2, 2]
queries:
(1, 3) -> 12 (5 + 5 + 2)
(0, 4) -> 17 (পুরো array)
(2, 2) -> 5 (একটাই element)
(0, 0) -> 3
edge: কোনো update নেই, n = 3 -> a = [0, 0, 0], যেকোনো query -> 0
edge: update (2, 2, 7) শেষ index -> a = [0, 0, 7], query (0, 2) -> 7
খেয়াল করো: update আর query-র off-by-one আলাদা আলাদা সামলাতে হয় — update-এ diff[r+1] -= x, query-তে P[qr+1] - P[ql]। দুটো convention এক না করে গুলিয়ে ফেললেই bug।
7. Brute force thinking¶
দুটোই সরাসরি loop দিয়ে: update-এ range ধরে যোগ, query-তে range ধরে যোগ:
def solve_brute(n, updates, queries):
a = [0] * n
for l, r, x in updates:
for i in range(l, r + 1): # update: range ধরে যোগ
a[i] += x
ans = []
for ql, qr in queries:
ans.append(sum(a[ql:qr + 1])) # query: range ধরে যোগ
return ans
সরল, ঠিক উত্তর দেয় — কিন্তু update আর query দুটোতেই range ধরে হাঁটছে।
8. Why brute force may be slow¶
ধরো q_u টা update, প্রতিটা O(n) → update-এ O(n × q_u)। আর q_q টা query, প্রতিটা O(n) → query-তে O(n × q_q)। মোট O(n × (q_u + q_q))।
n = 100000, q_u = q_q = 100000 হলে:
brute force: ~2 × 10^10 operation — TLE
pipeline : O(n + q_u) update-side + O(n + q_q) query-side ≈ 4×10^5 — দ্রুত
দুই জায়গাতেই একই অপচয়: overlapping range বারবার যোগ হচ্ছে। update-এ difference array সেটা কাটে, query-তে prefix sum কাটে।
9. Better thinking¶
মূল insight: দুই পর্যায়ে দুই কৌশল। update-গুলো difference array-তে O(1) করে জমাও, একবার prefix চালিয়ে final array পাও (069)। তারপর সেই final array-র উপর আরেকটা prefix বানিয়ে প্রতিটা query O(1) (068):
def solve(n, updates, queries):
diff = [0] * (n + 1)
for l, r, x in updates:
diff[l] += x
diff[r + 1] -= x
a = [0] * n
run = 0
for i in range(n):
run += diff[i]
a[i] = run
P = [0] * (n + 1) # final array-র prefix (query-র জন্য)
for i in range(n):
P[i + 1] = P[i] + a[i]
return [P[qr + 1] - P[ql] for ql, qr in queries]
মোট O(n + q_u + q_q) — সবগুলো ধাপ linear। brute-এর গুণফল থেকে যোগফলে নেমে এল।
10. Thinking tweak¶
আসল "আহা!" মুহূর্ত: update আর query আলাদা প্রকৃতির — তাদের আলাদা কৌশল দাও, আর ক্রমে সাজাও। "সব বদল আগে, তারপর সব প্রশ্ন" — এই বিভাজনটাই সরল কৌশলটাকে সম্ভব করে।
দ্বিতীয় tweak: prefix sum এখানে দুই ভূমিকায় — একবার difference-কে undo করে আসল array ফেরায়, আরেকবার সেই array-কে query-ready বানায়। একই হাতিয়ার, দুই কাজ। (যদি update আর query মাঝপথে মিশত, তখন এই ক্রম ভেঙে পড়ত — তখন BIT/segment tree।)
11. Step-by-step dry run¶
n = 5, updates (0, 2, 3), (1, 4, 2), query (1, 3)।
পর্যায় 1 — difference:
| update | diff পরিবর্তন | diff এখন |
|---|---|---|
| (0, 2, 3) | diff[0]+=3, diff[3]-=3 | [3, 0, 0, -3, 0, 0] |
| (1, 4, 2) | diff[1]+=2, diff[5]-=2 | [3, 2, 0, -3, 0, -2] |
পর্যায় 2 — prefix → final array:
| i | diff[i] | run | a[i] |
|---|---|---|---|
| 0 | 3 | 3 | 3 |
| 1 | 2 | 5 | 5 |
| 2 | 0 | 5 | 5 |
| 3 | -3 | 2 | 2 |
| 4 | 0 | 2 | 2 |
পর্যায় 3 — final array-র prefix P = [0, 3, 8, 13, 15, 17]। query (1, 3): P[4] - P[1] = 15 - 3 = 12। মিলিয়ে: 5+5+2 = 12 ✓।
12. Python solution¶
def solve(n, updates, queries):
"""পর্যায় 1: difference array দিয়ে update; পর্যায় 2: prefix → final array;
পর্যায় 3: final array-র prefix দিয়ে প্রতি query O(1)। সব O(n + q)।"""
diff = [0] * (n + 1)
for l, r, x in updates:
diff[l] += x
diff[r + 1] -= x
a = [0] * n # final array (update-পরবর্তী)
run = 0
for i in range(n):
run += diff[i]
a[i] = run
P = [0] * (n + 1) # final array-র prefix (query-র জন্য)
for i in range(n):
P[i + 1] = P[i] + a[i]
return [P[qr + 1] - P[ql] for ql, qr in queries]
def solve_brute(n, updates, queries):
"""ধীর version — মিলিয়ে দেখার জন্য।"""
a = [0] * n
for l, r, x in updates:
for i in range(l, r + 1):
a[i] += x
return [sum(a[ql:qr + 1]) for ql, qr in queries]
# --- ছোট test গুলো (সব pass করা উচিত) ---
n = 5
updates = [(0, 2, 3), (1, 4, 2)]
queries = [(1, 3), (0, 4), (2, 2), (0, 0)]
assert solve(n, updates, queries) == [12, 17, 5, 3]
assert solve(3, [], [(0, 2), (1, 1)]) == [0, 0] # update নেই
assert solve(3, [(2, 2, 7)], [(0, 2), (2, 2)]) == [7, 7] # শেষ index update
# fast আর brute একই উত্তর দেয় কিনা (random brute-force cross-check):
import random
random.seed(7)
def rand_range(m):
"""0..m-1 থেকে এলোমেলো (l, r) জোড়া, l <= r।"""
l = random.randint(0, m - 1)
r = random.randint(0, m - 1)
return (min(l, r), max(l, r))
for _ in range(300):
m = random.randint(1, 8)
ups = []
for _ in range(random.randint(0, 5)):
l, r = rand_range(m)
ups.append((l, r, random.randint(-5, 5)))
qs = [rand_range(m) for _ in range(random.randint(1, 5))]
assert solve(m, ups, qs) == solve_brute(m, ups, qs)
print(solve(n, updates, queries)) # [12, 17, 5, 3]
print(solve(3, [(2, 2, 7)], [(0, 2)])) # [7]
print("সব test pass!")
13. Line-by-line explanation¶
পর্যায় 1 — 069-এর difference array। প্রতিটা update দুটো দাগে, O(1)।
পর্যায় 2 — diff-এর উপর prefix sum চালিয়ে update-পরবর্তী আসল array a।
পর্যায় 3-এর প্রস্তুতি — final array a-র prefix P (067)। এবার যেকোনো range sum এক বিয়োগে।
প্রতিটা query: P[qr+1] - P[ql] (068)। সব O(1)।
বাকি assert গুলো নির্দিষ্ট উদাহরণ + random brute-force cross-check দিয়ে fast আর brute মিলিয়ে দেখছে। সব মিললে সব test pass!।
14. Output walkthrough¶
solve(5, [(0,2,3),(1,4,2)], [(1,3),(0,4),(2,2),(0,0)]):
- update →
diff = [3, 2, 0, -3, 0, -2] - prefix →
a = [3, 5, 5, 2, 2] - final prefix →
P = [0, 3, 8, 13, 15, 17] - queries →
[12, 17, 5, 3]→ প্রথমprint solve(3, [(2,2,7)], [(0,2)])→a = [0,0,7], query →[7]→ দ্বিতীয়print- সব
assertpass →সব test pass!
ছাপা output:
15. Time complexity¶
O(n + q_u + q_q) — update O(q_u) + দুটো prefix pass O(n) + query O(q_q)। সবগুলো ধাপ linear, কোনো nested loop নেই। brute-এর O(n × (q_u + q_q)) থেকে গুণফল-কে-যোগফলে নামার সেই চেনা লাফ।
16. Space complexity¶
O(n) — diff, a, P তিনটাই O(n) array। চাইলে a-কেই reuse করে একটা কমানো যায়, কিন্তু আলাদা রাখা পরিষ্কার।
17. Common mistakes¶
- পর্যায়ের ক্রম গুলিয়ে ফেলা — সব update আগে শেষ করো, তারপর prefix, তারপর query। মাঝপথে query করলে diff তখনো final array না।
- update-এর off-by-one আর query-র off-by-one মেলানো — update-এ
diff[r+1] -= x, query-তেP[qr+1] - P[ql]; দুটো আলাদা, গুলিয়ো না। - final array-র prefix না বানিয়ে সরাসরি
a-তে query loop — তাহলে query আবার O(n), pipeline-এর লাভ হারালে। diffn ঘর বানানো —rশেষ index হলেdiff[r+1]out-of-range; n+1 রাখো।- update আর query মাঝপথে মেশা ধরে নেওয়া — এই সরল কৌশল তখন চলে না; প্রশ্নে "আগে সব update" না থাকলে BIT/segment tree ভাবো।
18. Harder problems that inherit this idea¶
- CSES — Range Update Queries (https://cses.fi/problemset/task/1651) — update + point query; difference/BIT-এর ধারণা গভীরে নেয়।
- LeetCode — Range Addition (https://leetcode.com/problems/range-addition/) — pipeline-এর update অংশ।
- 079 (Imos Method Basic) আর 080 (Sweep Line Intro) — এই repo-তে পরের ধাপ: difference-চিন্তা event-এর জগতে।
19. Pattern learned¶
Difference-array update + prefix-sum query pipeline — update O(1) দাগে জমাও, prefix-এ final array, আবার prefix-এ query। বড় শিক্ষা: update আর query আলাদা প্রকৃতির; "সব বদল আগে, সব প্রশ্ন পরে" সাজালে দুই কৌশল সুন্দর জোড়া লাগে।
20. Final summary¶
ভবিষ্যতের আমাকে এক লাইনে: "range update + পরে range query = difference array (update) → prefix (final array) → prefix (query)। সব O(n + q)। শর্ত: সব update আগে, তারপর query — নইলে BIT/segment tree।"
পরের ধাপ → 071 — Prefix XOR (যোগের জায়গায় XOR বসিয়ে একই template)।
ফিরে দেখা: শিকড় → 069 — Difference Array, 068 — Range Sum Query · এই level-এর map → ../README.md · concept ঝালাই → ../concept-notes.md · template → ../../../templates/math-problem-note-template.md।
মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখো; problem statement copy কোরো না; official link শুধু attribution-এর জন্য রাখো; "asked by Google/Amazon" এমন দাবি কোরো না — বরং "Google-style pattern" / "common interview pattern" লেখো।