Company Line Switching
A subway system has ten companies. Riding any number of stops within one company costs 1 yen. The moment you switch to a different company, that's another yen. The fare isn't per-edge --- it's per-session. This completely breaks the normal edge-weight model, and fixing it requires a clever node expansion.
The Problem: ARC061-C Snuke's Subway Trip

\(N\) stations, \(M\) connections. Each connection links two stations and belongs to a company. Riding within one company's network costs exactly 1 yen total, no matter how many stations you traverse. Switching companies at a station costs an additional 1 yen (for the new company's fare). Find the minimum cost from station 1 to station \(N\).
Why Naive Modeling Fails
Your first instinct: edge weight = 1 for every connection. But that charges per-edge, not per-session. If company A connects stations 1-2-3-4, the correct cost is 1 yen (one session). A naive model charges 3 yen (three edges).
Your second instinct: edge weight = 0 within a company, edge weight = 1 for switching. But how do you charge the initial boarding? The first company you ride also costs 1 yen.
Gotcha. The cost structure is "1 yen to enter a company's network, then ride free within it." This is a per-session cost, not a per-edge cost. You cannot model it with simple edge weights unless you add structure.
The Virtual Node Trick
For each (station, company) pair that exists in the input, create a virtual node. Then wire three types of edges:
- Enter a company: real station \(s\) \(\to\) virtual \((s, c)\), cost 1.
- Exit a company: virtual \((s, c)\) \(\to\) real station \(s\), cost 0.
- Ride within a company: virtual \((s_a, c)\) \(\leftrightarrow\) virtual \((s_b, c)\), cost 0.
This captures the fare model perfectly:
- Entering company \(c\) at station \(s\) costs 1 yen (edge type 1).
- Traveling from station \(s_a\) to \(s_b\) within company \(c\) is free (edge type 3).
- Switching to company \(d\) at station \(s_b\): exit \(c\) (free), enter \(d\) (1 yen).
real(1) --1--> virt(1,A) --0--> virt(2,A) --0--> real(2)
|
1 yen
v
virt(2,B) --0--> virt(3,B) --0--> real(3)
Key insight. The virtual node acts as a gateway. Entering it costs 1 yen (the session fee). Once inside, all movement within that company's virtual nodes is free. Exiting is free because you've already paid.
Why 0-1 BFS?
All edge weights are either 0 or 1. This is the textbook scenario for 0-1 BFS with a deque, giving \(O(V + E)\) instead of \(O((V + E) \log V)\) from Dijkstra.
- Weight-0 edges: push to front of deque.
- Weight-1 edges: push to back of deque.
The deque maintains the invariant that nodes are processed in non-decreasing distance order, just like Dijkstra's priority queue but without the log factor.
Building the Graph
int node_id = N; // real stations: 0 to N-1
map<pair<int,int>, int> virt;
for (auto& [a, b, c] : edges) {
// Create virtual nodes for (a, c) and (b, c)
if (!virt.count({a, c})) virt[{a, c}] = node_id++;
if (!virt.count({b, c})) virt[{b, c}] = node_id++;
}
int total = node_id;
vector<vector<pair<int,int>>> adj(total);
// Type 3: ride within company (cost 0)
for (auto& [a, b, c] : edges) {
int va = virt[{a, c}], vb = virt[{b, c}];
adj[va].push_back({vb, 0});
adj[vb].push_back({va, 0});
}
// Types 1 and 2: enter (cost 1) and exit (cost 0)
for (auto& [key, vid] : virt) {
int station = key.first;
adj[station].push_back({vid, 1}); // enter
adj[vid].push_back({station, 0}); // exit
}
Trace: Small Subway
Stations: 4. Companies: A, B.
Connections: 1-2 (A), 2-3 (A), 2-3 (B), 3-4 (B).
Virtual nodes: (1,A)=4, (2,A)=5, (3,A)=6, (2,B)=7, (3,B)=8, (4,B)=9.
Edges:
| From | To | Cost | Type |
|---|---|---|---|
| 4 (1,A) | 5 (2,A) | 0 | ride within A |
| 5 (2,A) | 6 (3,A) | 0 | ride within A |
| 7 (2,B) | 8 (3,B) | 0 | ride within B |
| 8 (3,B) | 9 (4,B) | 0 | ride within B |
| 0 (st.1) | 4 (1,A) | 1 | enter A at st.1 |
| 4 (1,A) | 0 (st.1) | 0 | exit A at st.1 |
| 1 (st.2) | 5 (2,A) | 1 | enter A at st.2 |
| 5 (2,A) | 1 (st.2) | 0 | exit A at st.2 |
| 1 (st.2) | 7 (2,B) | 1 | enter B at st.2 |
| 7 (2,B) | 1 (st.2) | 0 | exit B at st.2 |
| 2 (st.3) | 6 (3,A) | 1 | enter A at st.3 |
| 6 (3,A) | 2 (st.3) | 0 | exit A at st.3 |
| 2 (st.3) | 8 (3,B) | 1 | enter B at st.3 |
| 8 (3,B) | 2 (st.3) | 0 | exit B at st.3 |
| 3 (st.4) | 9 (4,B) | 1 | enter B at st.4 |
| 9 (4,B) | 3 (st.4) | 0 | exit B at st.4 |
0-1 BFS from station 0 (real node for station 1):
| Step | Pop | dist | Deque state after |
|---|---|---|---|
| 1 | 0 (st.1), d=0 | dist[4]=1 | [4] |
| 2 | 4 (1,A), d=1 | dist[5]=1, dist[0] skip | [5, ...] front-push 5, 0 |
| 3 | 5 (2,A), d=1 | dist[6]=1, dist[1]=1 | [6, 1, ...] |
| 4 | 6 (3,A), d=1 | dist[2]=1 | [2, 1, ...] |
| 5 | 2 (st.3), d=1 | dist[8]=2 | [1, ..., 8] |
| 6 | 1 (st.2), d=1 | dist[7]=2 | [..., 7, 8] |
| 7 | 7 (2,B), d=2 | dist[8]=2 skip | [..., 8] |
| 8 | 8 (3,B), d=2 | dist[9]=2 | [9, ...] |
| 9 | 9 (4,B), d=2 | dist[3]=2 | --- |
Answer: dist[3] = 2 yen.
Path: station 1 \(\xrightarrow{\text{enter A, 1 yen}}\) ride A to station 3 \(\xrightarrow{\text{exit A, free}}\) station 3 \(\xrightarrow{\text{enter B, 1 yen}}\) ride B to station 4. Total: 2 yen.
Node Count Analysis
How many virtual nodes do we create? For each edge \((a, b, c)\), we create at most 2 virtual nodes: \((a, c)\) and \((b, c)\). With \(M\) edges, we create at most \(2M\) virtual nodes. Total nodes: \(N + 2M\).
Total edges: \(M\) (ride-within) + \(2 \times |\text{virtual nodes}|\) (enter/exit). This is \(O(M)\).
0-1 BFS runs in \(O(N + M)\), which handles the constraints (\(N, M \leq 10^5\)) easily.
Gotcha. Don't create virtual nodes for every possible (station, company) pair. Only create them for pairs that actually appear in the input. Otherwise, you waste memory on stations that a company doesn't serve.
The General Pattern: Per-Session Costs
This virtual node trick works whenever a cost is charged per-session rather than per-edge:
- Toll roads: entering a toll network costs a flat fee. Driving within it is free.
- Subscription services: activating a service costs money. Using it is unlimited.
- Zone-based transit: entering a zone costs a fare. Travel within the zone is free.
The recipe is always the same:
- Create virtual nodes for (location, session-type) pairs.
- Entering a session costs the fee.
- Traveling within a session is free.
- Exiting a session is free.
- Run 0-1 BFS (or Dijkstra if costs vary).
Key insight. Per-session costs cannot be modeled with edge weights alone. You need the virtual node layer to "remember" which session you're currently in. The entry edge charges the fee; the internal edges provide free movement.
Exercises
-
Cost variation. If different companies charge different fares (company A = 3 yen, company B = 5 yen), what changes in the virtual node model?
-
Exit fee. If switching companies costs an additional penalty \(P\) at the exit (not the entry), how do you adjust edge weights?
-
Counting companies. How would you modify the approach to count the minimum number of distinct companies used (ignoring yen cost)?
Problems
| Problem | Link | Difficulty |
|---|---|---|
| ARC061-C — Snuke's Subway Trip | atcoder.jp/contests/arc061/tasks/arc061_c | Hard |
| LC 1129 — Shortest Path with Alternating Colors | leetcode.com/problems/shortest-path-with-alternating-colors | Medium |