Skip to content

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

Multiple transit lines with penalty cost for switching between lines

\(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:

  1. Enter a company: real station \(s\) \(\to\) virtual \((s, c)\), cost 1.
  2. Exit a company: virtual \((s, c)\) \(\to\) real station \(s\), cost 0.
  3. 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:

  1. Create virtual nodes for (location, session-type) pairs.
  2. Entering a session costs the fee.
  3. Traveling within a session is free.
  4. Exiting a session is free.
  5. 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

  1. Cost variation. If different companies charge different fares (company A = 3 yen, company B = 5 yen), what changes in the virtual node model?

  2. Exit fee. If switching companies costs an additional penalty \(P\) at the exit (not the entry), how do you adjust edge weights?

  3. 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