Skip to content

Commit 98a4566

Browse files
authored
Improve formatting and clarity in en.md
1 parent 3d2a737 commit 98a4566

1 file changed

Lines changed: 109 additions & 94 deletions

File tree

1101/en.md

Lines changed: 109 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
## Problem Summary
44

5-
We are given a connected weighted graph with (N) cities and (M) roads.
5+
We are given a connected weighted graph with `N` cities and `M` roads.
66
Each road has a *danger value*.
77

8-
For each query (s, t), we must find a path from (s) to (t) such that:
8+
For each query `s t`, we must find a path from `s` to `t` such that:
99

1010
> the maximum danger of any road on the path is minimized.
1111
@@ -19,7 +19,8 @@ If we build a **Minimum Spanning Tree (MST)** of the graph, then:
1919

2020
> For any two nodes, the path between them in the MST minimizes the maximum edge weight among all possible paths in the original graph.
2121
22-
This is a well-known property of MSTs.
22+
Why is this true? Intuitively, in an MST every edge is as "cheap" as possible: if there were a path between two nodes whose maximum edge could be made smaller by taking some other route in the original graph, we could replace the larger edge on the MST path with that smaller edge and still keep the graph connected. That would give us another spanning tree with strictly smaller maximum edge on that path, contradicting the fact that the original tree was minimum. So for any pair of nodes, the path inside the MST uses edges that are as small as possible, and the largest edge on that MST path is the smallest possible "bottleneck" among all paths in the original graph.
23+
2324
Therefore, once we construct the MST, we only need to answer queries on that tree.
2425

2526
---
@@ -31,21 +32,21 @@ Therefore, once we construct the MST, we only need to answer queries on that tre
3132
3. Preprocess the tree using **Binary Lifting**
3233
4. Store:
3334

34-
* `anc[u][i]`(2^i)-th ancestor of node (u)
35-
* `mx[u][i]` → maximum edge weight from (u) to its (2^i)-th ancestor
35+
* `anc[u][i]``2^i`-th ancestor of node `u`
36+
* `mx[u][i]` → maximum edge weight from `u` to its `2^i`-th ancestor
3637
5. For each query:
3738

38-
* compute LCA of (u) and (v)
39-
* find maximum edge on path (u to LCA) and (v to LCA) using binary lifting
39+
* compute LCA of `u` and `v`
40+
* find maximum edge on path (`u` to LCA) and (`v` to LCA) using binary lifting
4041
* answer = max of the two above values.
4142

4243
---
4344

4445
## Complexity
4546

46-
* MST construction: (O(M log N))
47-
* Binary lifting preprocessing: (O(N log N))
48-
* Each query: (O(log N))
47+
* MST construction: `O(M log N)`
48+
* Binary lifting preprocessing: `O(N log N)`
49+
* Each query: `O(log N)`
4950

5051
This easily fits within constraints.
5152

@@ -56,172 +57,186 @@ This easily fits within constraints.
5657
```cpp
5758
#include<bits/stdc++.h>
5859
using namespace std;
59-
#define br cout<<"\n";
60-
#define ll long long
61-
#define loop(n) for(int i=0; i<(n); i++)
62-
#define fr(i,init, n) for(int i=(init); i<(n); i++)
63-
#define revl(i,init) for(int i=(init-1); i>=0; i--)
64-
#define pb push_back
65-
#define all(v) v.begin(),v.end()
66-
#define nl "\n"
6760

61+
// Constants
62+
const int N = 50005, LOG = 18; // N = max nodes, LOG = ceil(log2(N)) for binary lifting
63+
64+
// Graph structures
65+
vector<pair<int,int>> mainGraph[N]; // Original graph edges: {neighbor, weight}
66+
vector<pair<int,int>> MST[N]; // Minimum Spanning Tree edges
67+
bool vis[N]; // Visited array for MST construction
6868

69-
const int N = 50005, LOG=18 ;
70-
vector<pair<int,int>> adj2[N], adj[N];
71-
bool vis[N];
72-
int anc[N][LOG], mx[N][LOG], dep[N];
69+
// Binary lifting tables
70+
int anc[N][LOG]; // anc[node][i] = 2^i-th ancestor of node
71+
int mx[N][LOG]; // mx[node][i] = maximum edge weight from node to its 2^i-th ancestor
72+
int dep[N]; // Depth of each node in the MST (rooted at 1)
7373

7474

75-
// clear global variables for each test case
76-
void clear(){
77-
loop(N){
75+
// Clear all globals for new test case
76+
void clear() {
77+
for(int i = 0; i < N; i++){
7878
vis[i] = false;
79-
adj2[i].clear();
80-
adj[i].clear();
79+
mainGraph[i].clear();
80+
MST[i].clear();
8181
}
82-
memset(anc,0, sizeof(anc));
82+
// due the the constraints of the problem its feasible to clear the entire thing upto N
83+
memset(anc, 0, sizeof(anc));
8384
memset(mx, 0, sizeof(mx));
8485
memset(dep, 0, sizeof(dep));
8586
}
8687

87-
// create the MST from adj2[] and build the new one in adj[]
88-
void createMST(){
89-
88+
89+
// Create MST using Prim's algorithm and store it in MST[]
90+
void createMST() {
91+
// Min-heap for Prim's: stores {weight, node, parent}
9092
priority_queue<
9193
tuple<int,int,int>,
9294
vector<tuple<int,int,int>>,
9395
greater<tuple<int,int,int>>
9496
> pq;
95-
96-
pq.push({-1, 1,0});
97-
98-
while(! pq.empty()){
97+
98+
pq.push({-1, 1, 0}); // Start from node 1, with "fake" edge weight -1
99+
100+
while(!pq.empty()){
99101
int wt, node, parent;
100-
tie(wt, node, parent) = pq.top();
102+
tie(wt, node, parent) = pq.top();
101103
pq.pop();
102-
103-
if(vis[node]) continue;
104-
104+
105+
if(vis[node]) continue; // Already included in MST
106+
105107
vis[node] = true;
106-
107-
if(parent!=0){
108-
adj[parent].pb({node,wt});
109-
adj[node].pb({parent,wt});
108+
109+
if(parent != 0){
110+
// Add edge to MST (undirected)
111+
MST[parent].push_back({node, wt});
112+
MST[node].push_back({parent, wt});
110113
}
111-
112-
113-
for(auto child: adj2[node]){
114+
115+
// Push all adjacent edges to PQ
116+
for(auto child: mainGraph[node]){
114117
if(!vis[child.first]){
115118
pq.push({child.second, child.first, node});
116119
}
117120
}
118121
}
119-
120122
}
121-
122-
// dfs for binary-lifting precomputations
123-
void dfs(int node, int par=0, int wt=0){
124-
125-
dep[node] = dep[par]+1;
126-
anc[node][0] = par;
127123

128-
mx[node][0]= wt;
129124

130-
fr(i,1,LOG){
131-
anc[node][i] = anc[ anc[node][i-1] ][i-1];
132-
mx[node][i] = max( mx[ anc[node][i-1] ][i-1] , mx[node][i-1] );
125+
// DFS on MST to compute depth, ancestor table, and max edge table for binary lifting
126+
void dfs(int node, int par = 0, int wt = 0){
127+
128+
dep[node] = dep[par] + 1; // Set depth
129+
anc[node][0] = par; // 2^0-th ancestor is immediate parent
130+
mx[node][0] = wt; // Max edge to parent
131+
132+
// Binary lifting preprocessing
133+
for(int i = 1; i < LOG; i++){
134+
anc[node][i] = anc[anc[node][i-1]][i-1]; // 2^i-th ancestor
135+
mx[node][i] = max(mx[anc[node][i-1]][i-1], mx[node][i-1]); // Max edge to 2^i-th ancestor
133136
}
134137

135-
for(auto child: adj[node]){
136-
if(child.first != par){
138+
// DFS to children
139+
for(auto child: MST[node]){
140+
if(child.first != par){ // Avoid going back to parent
137141
dfs(child.first, node, child.second);
138142
}
139143
}
140144
}
141145

146+
147+
// Find LCA (Lowest Common Ancestor) of two nodes using binary lifting
142148
int lca(int u, int v){
143-
if(dep[u]< dep[v]) swap(u,v);
149+
if(dep[u] < dep[v]) swap(u, v); // Make u deeper
144150

145-
revl(i,LOG){
146-
if( dep[ anc[u][i] ] >= dep[v] ){
151+
// Lift u up to same depth as v
152+
for(int i = LOG-1; i >= 0; i--){
153+
if(dep[anc[u][i]] >= dep[v]){
147154
u = anc[u][i];
148155
}
149156
}
150157

151-
if(u==v) return u;
158+
if(u == v) return u; // If one is ancestor of the other
152159

153-
revl(i,LOG){
160+
// Lift both u and v until their ancestors match
161+
for(int i = LOG-1; i >= 0; i--){
154162
if(anc[u][i] != anc[v][i]){
155163
u = anc[u][i];
156164
v = anc[v][i];
157165
}
158166
}
159167

160-
return anc[u][0];
161-
168+
return anc[u][0]; // Return their parent (LCA)
162169
}
163170

164-
// maximum edge between a node and its ancestor
165-
int maxedge(int node, int l){
166-
int dis = dep[node] - dep[l];
167171

172+
// Maximum edge weight on path from node to its ancestor 'l'
173+
int maxedge(int node, int l){
174+
int dis = dep[node] - dep[l]; // Distance from node to ancestor
168175
int ans = 0;
169176

170-
revl(i,LOG){
171-
if(dis & (1<<i)){
172-
ans = max( ans, mx[node][i] );
173-
node= anc[node][i];
177+
// Lift node up to ancestor while tracking max edge
178+
for(int i = LOG-1; i >= 0; i--){
179+
if(dis & (1 << i)){
180+
ans = max(ans, mx[node][i]);
181+
node = anc[node][i];
174182
}
175183
}
176-
177184
return ans;
178-
179185
}
180186

181-
int query(int u, int v){
182-
183-
int l = lca(u,v);
184-
185-
return max( maxedge(u,l), maxedge(v,l) );
186187

188+
// Query maximum edge weight between two nodes u and v
189+
int query(int u, int v){
190+
int l = lca(u, v); // Find LCA
191+
return max(maxedge(u, l), maxedge(v, l)); // Max edge from u->LCA and v->LCA
187192
}
188193

189194

190-
191-
int tc = 1;
195+
int tc = 1; // Test case counter
192196
void SOLVE(){
197+
cout << "Case " << tc++ << ":" << "\n";
198+
199+
int n, m;
200+
cin >> n >> m;
193201
clear();
194-
cout<<"Case "<<tc++<<":"<<nl;
195202

196-
int n,m; cin>>n>>m;
203+
// Read graph
197204
while(m--){
198-
int a,b,c; cin>>a>>b>>c;
199-
adj2[a].pb({b,c});
200-
adj2[b].pb({a,c});
205+
int a, b, c;
206+
cin >> a >> b >> c;
207+
mainGraph[a].push_back({b, c});
208+
mainGraph[b].push_back({a, c});
201209
}
202210

203211
createMST();
212+
dfs(1);
213+
// The original problem guarantees the graph (and thus the MST) is connected,
214+
// so we can safely root the DFS at node 1. If used on arbitrary graphs,
215+
// ensure connectivity or run DFS from all components.
216+
204217

205-
dfs(1);
218+
int q;
219+
cin >> q;
206220

207-
int q; cin>>q;
208221
while(q--){
209-
int u,v; cin>>u>>v;
210-
cout<<query(u,v)<<nl;
222+
int u, v;
223+
cin >> u >> v;
224+
cout << query(u, v) << "\n";
211225
}
212-
213226
}
214227

228+
215229
signed main(){
216230
ios_base::sync_with_stdio(0);
217231
cin.tie(0);
218-
int t=1;
219232

220-
cin>>t;/////////////////////
233+
int t;
234+
cin >> t;
221235

222236
while(t--) SOLVE();
223237
return 0;
224238
}
239+
225240
```
226241
227242
---

0 commit comments

Comments
 (0)