003 — Building Roads¶
Source¶
- Original source: CSES Problem Set — Building Roads
- Platform: CSES — https://cses.fi/problemset/task/1666
- Topic: disjoint set union
- Difficulty: Easy
- Pattern: components − 1 (connect all components)
- Basic idea inherited from:
../../09-graphs/-এর connected-components; এখানে component গুনে তাদের জোড়ার রাস্তা ছাপাই
1. Problem in simple words¶
n টা city আর m টা existing road দেওয়া। তোমাকে বলতে হবে — সব city-কে একসাথে যুক্ত করতে সবচেয়ে কম কয়টা নতুন road লাগবে, আর কোন কোন জোড়া city-র মধ্যে সেগুলো বানাতে হবে। (CSES-এ city 1..n; আমরা ভেতরে 0-indexed ধরব।)
n = 4, roads = (1-2) (3-4) [0-indexed: (0-1) (2-3)]
দুটো আলাদা দল: {1,2} আর {3,4}
এক রাস্তা যোগ করলেই সব যুক্ত -> উত্তর: 1 রাস্তা, যেমন (2-4)
2. Which basic idea does this inherit from?¶
মূল ভিত হলো connected components বের করা — ../../09-graphs/-এর সেই কাজ। যখন তুমি জানো কয়টা আলাদা দল আছে, তখন তাদের একটা শেকলে গাঁথতে লাগে ঠিক (দল − 1) টা রাস্তা। DSU দল গোনে আর প্রতি দলের একজন প্রতিনিধি দেয়।
3. What is the hidden math or logic?¶
লুকানো logic: c টা আলাদা component-কে একটা connected graph বানাতে ন্যূনতম c − 1 edge লাগে — ঠিক যেমন c টা গাছকে একটা বনে নয়, একটা শেকলে বাঁধতে c−1 দড়ি লাগে। বেশি দিলে cycle তৈরি হয় (অপচয়); কম দিলে কেউ বিচ্ছিন্ন থেকে যায়। তাই minimum = c − 1, আর প্রতিটা নতুন road দুটো ভিন্ন component জোড়ে।
4. Which data structure is needed?¶
একটা DSU (parent + size)। প্রথমে existing road union করি; তারপর প্রতিটা component থেকে একজন প্রতিনিধি তুলে তাদের পরপর জুড়ে নতুন road-এর তালিকা বানাই।
5. Why this data structure fits¶
এটা বিশুদ্ধ component প্রশ্ন — "কয়টা দল আর কাকে কার সাথে জুড়ব?" DSU merge-only দুনিয়ায় road union করে দল গড়ে, আর find দিয়ে প্রতি node-এর প্রতিনিধি দেয়। প্রতিনিধি দেখে আমরা প্রতি দল থেকে একটা করে node বেছে শেকল বানাতে পারি — পথ/দূরত্ব ছাড়াই।
6. Real-life analogy¶
ভাবো কয়েকটা আলাদা দ্বীপপুঞ্জ, প্রতিটায় ভেতরে সেতু আছে কিন্তু একে অপরের সাথে নেই। সব দ্বীপপুঞ্জকে এক করতে তুমি একটার সাথে আরেকটাকে সেতুতে বাঁধো — c টা দ্বীপপুঞ্জ হলে c − 1 সেতুই যথেষ্ট, একটা লম্বা শেকলের মতো।
7. Visual explanation¶
existing road union করো, তারপর প্রতি component-এর এক প্রতিনিধি নিয়ে পরপর জোড়ো।
n=5, existing roads: (0-1) (2-3)
শুরু: *0 *1 *2 *3 *4 parent: [0,1,2,3,4] components = 5
union(0,1): *0 *2 *3 *4 parent: [0,0,2,3,4] components = 4
|
1
union(2,3): *0 *2 *4 parent: [0,0,2,2,4] components = 3
| |
1 3
প্রতিনিধি: find(0)=0, find(2)=2, find(4)=4 -> reps = [0, 2, 4]
শেকল: (0-2) (2-4) -> নতুন road = 2 (= components − 1)
*0----নতুন----*2----নতুন----*4
| |
1 3 এখন সব এক component
8. Example input and output¶
Input : n=4, roads=[(0,1),(2,3)]
Output: 1, [(0,2)]
Input : n=5, roads=[]
Output: 4, [(0,1),(1,2),(2,3),(3,4)] (সবাই একা -> শেকল)
Input : n=3, roads=[(0,1),(1,2)]
Output: 0, [] (আগেই যুক্ত)
9. Brute force thinking¶
সরল চিন্তা: adjacency list বানাও, DFS/BFS দিয়ে component বের করো, প্রতিটা component-এর একটা node তালিকায় রাখো, তারপর পরপর জুড়ে দাও।
1) DFS দিয়ে প্রতিটা component label করো
2) প্রতি component থেকে একটা প্রতিনিধি নাও
3) প্রতিনিধিদের পরপর জুড়ে c−1 টা road ছাপাও
10. Why brute force is slow¶
DFS version ঠিকঠাক — O(n + m)। কিন্তু CSES-এর scale-এ (n, m ১০ লাখ পর্যন্ত) recursive DFS-এ stack overflow-এর ভয়, আর adjacency list গড়ার overhead। DSU iterative, array-ভিত্তিক, recursion-হীন — বড় input-এ নিরাপদ ও পরিষ্কার, একই O(n + m)।
11. Key observation¶
মূল observation: উত্তরের সংখ্যাটা শুধু component-সংখ্যার উপর নির্ভর — c − 1। কোন road কোথায় বানাবে তার অসংখ্য valid উত্তর; সবচেয়ে সহজ হলো প্রতি component-এর এক প্রতিনিধিকে একটা শেকলে গাঁথা।
12. Optimized intuition¶
সব existing road union করো। তারপর find(i) দিয়ে প্রতিটা distinct root-এর প্রথম node সংগ্রহ করো — এরা প্রতিনিধি। প্রতিনিধি k টা হলে তাদের পরপর জুড়ে k − 1 road বানাও। পুরোটা amortized প্রায় O(n + m)।
13. Thinking tweak¶
মোচড়: "প্রতিটা জোড়া city-র জন্য কি আলাদা ভাবতে হবে?" — না। প্রশ্নটাকে নামিয়ে আনো "কয়টা দল আছে?"-এ। দল c হলে উত্তর সবসময় c − 1; বাকিটা শুধু প্রতিনিধি বেছে শেকল গাঁথা।
14. Step-by-step dry run¶
Input n=5, roads=[(0,1),(2,3)]:
union(0,1)→ {0,1};union(2,3)→ {2,3}; node 4 একা- প্রতিনিধি খোঁজা: find(0)=0 (নতুন), find(1)=0 (পুরোনো), find(2)=2 (নতুন), find(3)=2 (পুরোনো), find(4)=4 (নতুন)
- reps = [0, 2, 4]
- শেকল: (0,2), (2,4) → নতুন road = 2
- return
2, [(0,2),(2,4)]
15. Python solution¶
class DSU:
def __init__(self, n):
self.parent = list(range(n)) # প্রথমে সবাই নিজের নিজের root
self.size = [1] * n
def find(self, x): # representative + path compression
while self.parent[x] != x:
self.parent[x] = self.parent[self.parent[x]] # path halving
x = self.parent[x]
return x
def union(self, x, y): # union by size; merge হলে True
rx, ry = self.find(x), self.find(y)
if rx == ry:
return False
if self.size[rx] < self.size[ry]:
rx, ry = ry, rx
self.parent[ry] = rx
self.size[rx] += self.size[ry]
return True
def building_roads(n, roads):
dsu = DSU(n)
for u, v in roads:
dsu.union(u, v) # existing road দিয়ে দল গড়ো
reps = []
seen = set()
for city in range(n): # প্রতি component-এর প্রথম node
r = dsu.find(city)
if r not in seen:
seen.add(r)
reps.append(city)
new_roads = [(reps[i], reps[i + 1]) for i in range(len(reps) - 1)]
return len(new_roads), new_roads
# ---- tests ----
assert building_roads(4, [(0, 1), (2, 3)]) == (1, [(0, 2)])
assert building_roads(5, []) == (4, [(0, 1), (1, 2), (2, 3), (3, 4)])
assert building_roads(3, [(0, 1), (1, 2)]) == (0, []) # আগেই যুক্ত
assert building_roads(1, []) == (0, []) # একটাই city
print("সব test pass!")
16. Line-by-line code explanation¶
for u, v in roads: dsu.union(u, v)— সব existing road দিয়ে component গড়ো।seenset +repslist — প্রতিটা distinct root-এর জন্য একবার করে একটা প্রতিনিধি city তুলে রাখি।if r not in seen— একই root আগে দেখা থাকলে আবার নেব না; তাই reps-এ প্রতি component থেকে ঠিক একজন।new_roads = [(reps[i], reps[i+1]) ...]— প্রতিনিধিদের পরপর জুড়ে শেকল;kপ্রতিনিধি →k − 1road।return len(new_roads), new_roads— সংখ্যা আর তালিকা দুটোই।
17. Output walkthrough¶
প্রথম test-এ দুই component {0,1},{2,3}; reps=[0,2]; এক road (0,2)। দ্বিতীয়টায় ৫ জন একা; reps=[0,1,2,3,4]; চার road শেকলে। তৃতীয়টায় সব যুক্ত; reps=[0]; কোনো road নয়। single-city-ও 0। শেষে print: সব test pass!।
18. Time complexity¶
O((n + m) · α(n)) ≈ O(n + m) — m টা union, n টা find প্রতিনিধি খুঁজতে; প্রতিটা amortized near-constant।
19. Space complexity¶
O(n) — parent + size + seen + reps, সবই O(n)।
20. Common mistakes¶
- উত্তর
cবলা,c − 1নয় — একটা শেকল গাঁথতে দল-সংখ্যার চেয়ে একটা কম দড়ি লাগে। - প্রতিনিধি বাছার সময়
findনা করে কাঁচাparent[city]দেখা — flat না হলে ভুল প্রতিনিধি। - CSES-এর 1-indexed city ভুলে 0-indexed ছাপানো (input/output-এ ±1 মেলানো জরুরি)।
21. Which basic problem this inherits from¶
ভিত্তি: ../../09-graphs/-এর connected components। DFS-এ তুমি component label করতে; এখানে DSU দিয়ে union করে প্রতিনিধি তুলছ। উত্তরের অঙ্ক (c−1) একই থাকে। বড় CP-scale input-এ DSU-র iterative রূপ recursive DFS-এর চেয়ে নিরাপদ।
22. Similar harder problems¶
- Number of Operations to Make Network Connected (c−1, তবে spare edge গুনে) — https://leetcode.com/problems/number-of-operations-to-make-network-connected/ (এই tracker-এর #6)
- Graph Valid Tree (n−1 edge + zero cycle) — https://leetcode.com/problems/graph-valid-tree/ (#4)
- Road Construction (live component count, CP scale) — https://cses.fi/problemset/ (#17)
23. Pattern learned¶
Components − 1: c টা আলাদা দলকে যুক্ত করতে ঠিক c − 1 edge লাগে। DSU দিয়ে দল গুনে প্রতিনিধি শেকলে গাঁথলেই উত্তর তৈরি — connectivity-construction-এর সবচেয়ে সাধারণ formula।
24. Final summary¶
ভবিষ্যতের আমাকে: "সব যুক্ত করতে কয়টা নতুন link?" দেখলে আগে component গোনো, তারপর উত্তর = c − 1। প্রতিনিধিদের পরপর জুড়লেই বানানোর road-এর তালিকা মিলবে। এটাই connect-everything প্রশ্নের base formula।
মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" দাবি নেই — এটা একটা common interview pattern।