075 — Contribution Technique Basic¶
এতক্ষণ prefix/difference দিয়ে "একই হিসাব বারবার কোরো না" শিখলে। এবার সম্পূর্ণ নতুন একটা চিন্তার মোচড়: প্রশ্নটাই উল্টে দাও। subarray ধরে ধরে না গুনে, প্রতিটা element-কে জিজ্ঞেস করো — "তুমি মোট কয়টা subarray-তে আছ?" এই একটা প্রশ্নের উত্তর
(i+1)*(n-i)— বাঁয়ে কত পছন্দ, ডানে কত পছন্দ। ছবিটা আঁকতে পারলে contribution-এর পুরো জগত খুলে যাবে।
Source¶
- Original source: Classic CP technique (contribution / per-element counting)
- Platform: Classic CP technique — https://usaco.guide/silver/more-prefix-sums
- Topic: Math-based programming fundamentals → Level 5: Prefix, Difference, Contribution
- Difficulty: Medium
- Pattern: contribution technique (count subarrays containing each index)
- Basic idea inherited from: 045 — counting / combinatorics basics
1. Problem in simple words¶
একটা array a (দৈর্ঘ্য n) দেওয়া। প্রতিটা index i-এর জন্য বলতে হবে — মোট কয়টা contiguous subarray-তে element a[i] আছে?
মানে কোনো মান যোগ-টোগ নয় এখনই; শুধু গোনা: index i-কে ঢাকে এমন subarray-র সংখ্যা। যেমন a = [1, 2, 3]-এ index 1 (মান 2) আছে 4টা subarray-তে: [2], [1,2], [2,3], [1,2,3]। এই গোনাটাই contribution technique-এর প্রথম ধাপ — পরের problem (076)-এ এই count দিয়ে আসল কাজ হবে।
2. What is the math idea?¶
মূল idea হলো per-element counting via independent choices (স্বাধীন পছন্দের গুণফল)।
একটা subarray ঠিক হয় দুটো জিনিসে: শুরুর index l আর শেষের index r। index i-কে ঢাকতে হলে l ≤ i ≤ r হতে হবে। তাহলে:
l আর r স্বাধীনভাবে বাছা যায়, তাই গুণ:
এটা combinatorics-এর সরল গুণনীতি (rule of product): দুটো স্বাধীন পছন্দ মিলে মোট সংখ্যা = পছন্দ১ × পছন্দ২।
3. Which basic idea does this inherit from?¶
মূলত 045 (counting / combinatorics basics) থেকে — "স্বাধীন পছন্দ থাকলে গুণ করো" এই গুণনীতি। এখানে দুটো স্বাধীন পছন্দ: subarray-র বাঁ প্রান্ত আর ডান প্রান্ত।
prefix/difference থেকে এটা আলাদা চিন্তা — সেখানে "running total"; এখানে "কে কয়টায় আছে"। কিন্তু দুটোরই উদ্দেশ্য এক: O(n²) কাজকে O(n)-এ নামানো। contribution technique পুরো "sum over all subarrays" ঘরানার মাস্টার কী — আর তার ভিত্তি এই সহজ গোনা।
4. Real-life analogy¶
ভাবো একটা লম্বা ট্রেনে n টা বগি, পরপর সাজানো (index 0 থেকে n-1)। একটা "টিকিট" মানে পরপর কয়েকটা বগির একটা টানা অংশ — শুরুর বগি থেকে শেষের বগি পর্যন্ত।
তুমি জানতে চাও: বগি নম্বর i মোট কয়টা টিকিটে পড়ে? টিকিটের শুরু হতে হবে বগি i বা তার বাঁয়ে — মানে 0 থেকে i পর্যন্ত, (i+1) রকম শুরু। আর শেষ হতে হবে বগি i বা তার ডানে — i থেকে n-1 পর্যন্ত, (n-i) রকম শেষ।
শুরু আর শেষ আলাদাভাবে বাছাই, তাই মোট টিকিট = (i+1) × (n-i)। প্রতিটা বগির জন্য আলাদা করে গুনলে — মাঝের বগিগুলো বেশি টিকিটে পড়ে (বেশি option), প্রান্তের বগি কম।
5. Visual explanation¶
a = [1, 2, 3], n = 3। index 1 (মান 2)-কে কয়টা subarray ঢাকে?
index: 0 1 2
a: [ 1 ] [ 2 ] [ 3 ]
↑
এই element কোথায় কোথায় আছে?
শুরু l বাছা (≤ 1): l = 0 বা l = 1 → 2 রকম (i + 1 = 1 + 1 = 2)
শেষ r বাছা (≥ 1): r = 1, 2 → 2 রকম (n - i = 3 - 1 = 2)
মোট = 2 × 2 = 4 টা subarray:
l=0,r=1: [1,2]
l=0,r=2: [1,2,3]
l=1,r=1: [2]
l=1,r=2: [2,3] ✓ চারটাই index 1 ঢাকে
প্রতিটা index-এর জন্য count:
index i: 0 1 2
(i + 1): 1 2 3 ← বাঁয়ে শুরুর option
(n - i): 3 2 1 ← ডানে শেষের option
count = গুণ: 3 4 3 ← এই index কয়টা subarray-তে
মোট subarray = 3 + 4 + 3 = ... না! মোট subarray = n(n+1)/2 = 6।
(count-এর যোগফল ≠ subarray সংখ্যা; এটা "element-উপস্থিতি"-র যোগফল।)
মূল কথা: মাঝের index বেশি subarray-তে (বেশি option দুই পাশে), প্রান্তের কম। ছবিটা ধরো — বাঁয়ে পছন্দ × ডানে পছন্দ।
6. Example input and output¶
a = [1, 2, 3], n = 3
count per index = [3, 4, 3] ((i+1)*(n-i))
a = [10], n = 1
count = [1] (একটাই subarray, একটাই element)
a = [5, 6, 7, 8], n = 4
count = [4, 6, 6, 4] (1·4, 2·3, 3·2, 4·1)
a = [1, 1], n = 2
count = [2, 2] (1·2, 2·1)
লক্ষ করো count সবসময় symmetric (আয়নার মতো) — প্রান্ত থেকে কেন্দ্রের দিকে বাড়ে, কারণ কেন্দ্রের element-এর দুই পাশে বেশি জায়গা। আর count আসল মান a[i]-এর উপর নির্ভর করে না, শুধু position-এর উপর।
7. Brute force thinking¶
প্রতিটা index-এর জন্য সব subarray ঘুরে গুনি কোনগুলো ঢাকে:
def count_brute(a):
n = len(a)
counts = [0] * n
for l in range(n):
for r in range(l, n): # প্রতিটা subarray (l..r)
for i in range(l, r + 1):
counts[i] += 1 # এই subarray যাদের ঢাকে, সবার count++
return counts
সরল, ঠিক উত্তর দেয় — প্রতিটা subarray ধরে তার ভেতরের সব index-এর count বাড়ায়।
8. Why brute force may be slow¶
তিনটা nested loop! বাইরের দুটো subarray (n²/2 টা), ভেতরেরটা subarray-র দৈর্ঘ্য ধরে — মোট O(n³)। (এমনকি ভেতরের loop বাদ দিয়ে শুধু subarray গুনলেও O(n²)।)
n = 1000 হলে:
brute force (O(n³)): ~10^9 operation — খুব ধীর
formula way (O(n)) : ~1000 operation — চোখের পলকে
অপচয়টা স্পষ্ট: একই index বারবার গোনা হচ্ছে নানা subarray-তে। অথচ একটা সহজ সূত্রেই গোনার সংখ্যা সরাসরি বেরিয়ে আসে।
9. Better thinking¶
মূল insight: index i-কে গুনতে subarray ঘুরো না — শুধু গণনা করো কয়ভাবে বাঁ প্রান্ত আর ডান প্রান্ত বাছা যায়। (i+1) রকম শুরু, (n-i) রকম শেষ, স্বাধীন তাই গুণ:
প্রতিটা index-এর জন্য একটা গুণ — মোট O(n)। O(n³) (বা O(n²)) থেকে নেমে এল এক সূত্রে।
10. Thinking tweak¶
আসল "আহা!" মুহূর্ত: subarray গুনতে subarray ঘুরো না — প্রতিটা element-কে জিজ্ঞেস করো "তুমি কয়টায় আছ?" দৃষ্টিভঙ্গি subarray থেকে element-এ সরালে গোনাটা দুটো স্বাধীন পছন্দের গুণফলে নেমে আসে।
আর সেই গুণফল মনে রাখার চাবি: (i + 1) * (n - i) — বাঁয়ে i+1 রকম শুরু (index 0 থেকে i), ডানে n-i রকম শেষ (index i থেকে n-1)। মুখস্থ কোরো না, ছবি আঁকো — কোন প্রান্তে কতগুলো option। এই "প্রশ্ন উল্টে দাও" tweak-টাই contribution technique-এর প্রাণ, যা 076-077-এ আসল কাজে লাগবে।
11. Step-by-step dry run¶
a = [5, 6, 7, 8], n = 4। প্রতিটা index-এর count:
| i | i + 1 (বাঁ option) | n - i (ডান option) | count = (i+1)*(n-i) |
|---|---|---|---|
| 0 | 1 | 4 | 1 × 4 = 4 |
| 1 | 2 | 3 | 2 × 3 = 6 |
| 2 | 3 | 2 | 3 × 2 = 6 |
| 3 | 4 | 1 | 4 × 1 = 4 |
ফল: [4, 6, 6, 4]। যাচাই index 1: subarray গুলো যেগুলো index 1 ঢাকে — শুরু {0,1} × শেষ {1,2,3} = 2×3 = 6টা ✓। symmetric pattern (4,6,6,4) চোখে পড়ছে।
12. Python solution¶
def count_subarrays_with_index(a):
"""প্রতিটা index i কয়টা subarray-তে আছে: (i+1)*(n-i)। O(n)।"""
n = len(a)
return [(i + 1) * (n - i) for i in range(n)]
def count_brute(a):
"""ধীর O(n^3) version — প্রতিটা subarray ধরে গোনা; মিলিয়ে দেখার জন্য।"""
n = len(a)
counts = [0] * n
for l in range(n):
for r in range(l, n):
for i in range(l, r + 1):
counts[i] += 1
return counts
# --- ছোট test গুলো (সব pass করা উচিত) ---
assert count_subarrays_with_index([1, 2, 3]) == [3, 4, 3]
assert count_subarrays_with_index([10]) == [1]
assert count_subarrays_with_index([5, 6, 7, 8]) == [4, 6, 6, 4]
assert count_subarrays_with_index([1, 1]) == [2, 2]
assert count_subarrays_with_index([]) == [] # খালি array
# count আসল মানের উপর নির্ভর করে না, শুধু length-এর উপর:
assert count_subarrays_with_index([99, -7, 0]) == count_subarrays_with_index([1, 2, 3])
# fast আর brute একই উত্তর কিনা (brute-force cross-check):
for length in range(0, 9):
arr = list(range(length)) # মান যাই হোক, count একই
assert count_subarrays_with_index(arr) == count_brute(arr)
# প্রতিটা count-এর যোগফল = সব subarray-র মোট দৈর্ঘ্য (আরেকটা সঙ্গতি-চেক):
def total_length_brute(a):
n = len(a); tot = 0
for l in range(n):
for r in range(l, n):
tot += (r - l + 1)
return tot
for length in range(0, 9):
arr = list(range(length))
assert sum(count_subarrays_with_index(arr)) == total_length_brute(arr)
print(count_subarrays_with_index([1, 2, 3])) # [3, 4, 3]
print(count_subarrays_with_index([5, 6, 7, 8])) # [4, 6, 6, 4]
print("সব test pass!")
13. Line-by-line explanation¶
পুরো সমাধান এক লাইনে। প্রতিটা index i-এর জন্য: (i + 1) = বাঁয়ে শুরু বাছার রকম (index 0 থেকে i), (n - i) = ডানে শেষ বাছার রকম (index i থেকে n-1)। স্বাধীন পছন্দ, তাই গুণ — সেটাই index i-কে ঢাকা subarray-র সংখ্যা।
ধীর brute — প্রতিটা subarray (l..r) ধরে, তার ভেতরের সব index-এর count বাড়ায়। তিন স্তরের loop, শুধু সূত্র মিলিয়ে দেখার জন্য।
বাকি assert গুলো নির্দিষ্ট উদাহরণ + সব length-এ fast আর brute মিলিয়ে দেখছে (brute-force cross-check), আর দুটো সঙ্গতি-চেক: count আসল মান-নিরপেক্ষ, এবং count-এর যোগফল = সব subarray-র মোট দৈর্ঘ্য। সব মিললে সব test pass!।
14. Output walkthrough¶
a = [1, 2, 3]:
- i=0:
1 × 3 = 3 - i=1:
2 × 2 = 4 - i=2:
3 × 1 = 3 - ফল
[3, 4, 3]→ প্রথমprint [5,6,7,8]→[4, 6, 6, 4]→ দ্বিতীয়print- সব
assertpass →সব test pass!
ছাপা output:
15. Time complexity¶
O(n) — প্রতিটা index-এর জন্য একটা গুণ, n টা index। brute force-এর O(n³) (বা subarray-শুধু-গুনলে O(n²)) থেকে বিশাল উন্নতি।
16. Space complexity¶
O(n) — n দৈর্ঘ্যের count array ফেরত দিচ্ছি। শুধু count চাইলে এটুকুই; প্রতিটা আলাদা ছাপলে O(1) extra-ও সম্ভব।
17. Common mistakes¶
(i + 1) * (n - i)আরi * (n - i - 1)গুলিয়ে ফেলা — বাঁয়ে optioni+1(index 0..i), ডানেn-i(index i..n-1)। ছবি আঁকো, মুখস্থ কোরো না।- 0-indexing vs 1-indexing — এই সূত্র 0-indexed। 1-indexed হলে
i * (n - i + 1)হয়; convention মিলিয়ে নাও। - count-এর যোগফল = subarray সংখ্যা ভাবা — না; count-এর যোগফল = সব subarray-র মোট দৈর্ঘ্য। subarray সংখ্যা আলাদা:
n(n+1)/2। - count আসল মানের উপর নির্ভর ভাবা — count শুধু position-নির্ভর;
a[i]-র মান এখানে অপ্রাসঙ্গিক (মান লাগবে 076-এ গুণ করার সময়)। - প্রান্ত off-by-one — index 0-এর বাঁয়ে শুরু মাত্র 1 রকম (নিজেই), index n-1-এর ডানে শেষ মাত্র 1 রকম; সূত্র এটা ঠিক দেয় কিনা প্রান্তে যাচাই করো।
18. Harder problems that inherit this idea¶
- 076 (Sum of All Subarrays) — এই count-কে
a[i]দিয়ে গুণ করে সব subarray-র sum-এর যোগফল (পরের ধাপ)। - 078 (Pair Contribution Problems) — element-এর বদলে pair ধরে একই "কে কয়টায় আছে" চিন্তা।
- LeetCode — Sum of Subarray Minimums (https://leetcode.com/problems/sum-of-subarray-minimums/) — contribution + span; এই গোনার কঠিন রূপ (077)।
19. Pattern learned¶
Contribution technique (per-element counting) — প্রতিটা index কয়টা subarray-তে আছে = (i+1)*(n-i) (বাঁ option × ডান option)। বড় শিক্ষা: subarray ঘুরে না গুনে প্রশ্নটা উল্টে দাও — প্রতিটা element-কে জিজ্ঞেস করো "তুমি কয়টায় আছ?"; দুটো স্বাধীন পছন্দের গুণফলেই উত্তর।
20. Final summary¶
ভবিষ্যতের আমাকে এক লাইনে: "contribution-এর ভিত্তি = index i আছে (i+1)*(n-i) টা subarray-তে (বাঁয়ে i+1 শুরু, ডানে n-i শেষ)। subarray ঘুরো না, প্রশ্ন উল্টে element গোনো — O(n)।"
পরের ধাপ → 076 — Sum of All Subarrays (এই count-কে a[i] দিয়ে গুণ করে সব subarray-র sum-এর যোগফল)।
ফিরে দেখা: এই 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" লেখো।