MrStark2244's blog

By MrStark2244, history, 16 months ago, In English

⚔️ E. Quarrel — Synchronized BFS with Two Agents

Hello Codeforces community!

Today, we’ll dive into a unique and elegant graph problem — one that combines classic BFS with a twist: two agents moving simultaneously, and we must avoid collisions not only in space, but also in time.

This is problem E. Quarrel, and it’s not just about finding shortest paths — it’s about simulating two people who hate each other. https://codeforces.me/problemset/problem/29/E


Problem Summary

You're given a graph with n nodes and m edges: - Bob starts at node 1 and wants to go to node n. - Alex starts at node n and wants to go to node 1. - They must move at the same speed and must not be at the same node at the same time. - They can cross each other mid-edge (pass in opposite directions), but not arrive at or leave from the same node simultaneously.

You must: - Find shortest-length paths (with equal length k) for both Alex and Bob. - Ensure they don’t collide in any node at the same time.


Intuition

At first glance, this feels like two BFS traversals. But the constraint that they must not be at the same place at the same time makes this problem richer:

  • We want to compute a path for Bob and a path for Alex — same length, start at opposite ends, no overlap at same timestep.
  • Since they move simultaneously, we need to simulate time steps and track positions at each time.
  • So this becomes a synchronized simulation problem.

Step-by-Step Strategy

Step 1: Precompute Shortest Paths with BFS

Run BFS from node 1 → dist_bob[]
Run BFS from node n → dist_alex[]

These arrays tell us: - dist_bob[v] = min steps from 1 to v - dist_alex[v] = min steps from n to v


Step 2: Find the Shortest Valid Length k

We want the smallest k such that: - There’s at least one path of length k from 1 → n for Bob. - And a reverse path of same length k from n → 1 for Alex. - And at every time t (0 to k), bob_path[t] != alex_path[t]

We can reconstruct all shortest paths using parent arrays, or greedily choose any using BFS traversal order.


Step 3: Reconstruct Both Paths

Use BFS + parent tracking to reconstruct: - A path of length k for Bob from 1 → n - A path of length k for Alex from n → 1

Store these as: cpp vector<int> bob_path(t + 1); vector<int> alex_path(t + 1);

Step 4: Simulate Movement and Check for Conflicts

For i = 0 to k:

If bob_path[i] == alex_path[i]→ conflict

If i > 0 and bob_path[i-1] == alex_path[i] && alex_path[i-1] == bob_path[i] → conflict (crossing same edge in same step)

If no conflict → valid answer.

C++ code

#include <bits/stdc++.h> // Includes most standard libraries

// Using the standard namespace to avoid std:: prefix
using namespace std;

// Max N for convenience, typically N+1 for 1-based indexing
const int MAXN = 501;

// dist[bob_pos][alex_pos] = min_time
// Initialized to -1 (unvisited) by default for global arrays, but explicit initialization is safer.
int dist[MAXN][MAXN]; 
// parent[bob_pos][alex_pos] = {prev_bob_pos, prev_alex_pos}
pair<int, int> parent[MAXN][MAXN]; 

// Adjacency list for the graph
vector<int> adj[MAXN];

int main() {
    // Optimize C++ standard streams for competitive programming
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, m;
    cin >> n >> m;

    // Read roads and build the adjacency list
    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    // Initialize dist array with -1 (unvisited)
    // Using memset for efficiency on large arrays
    memset(dist, -1, sizeof(dist));

    // BFS queue stores pairs of (Bob's position, Alex's position)
    queue<pair<int, int>> q;

    // Starting state: Bob at 1, Alex at n, time 0
    dist[1][n] = 0;
    q.push({1, n});

    int final_k = -1; // Stores the minimum time (length of routes)

    // BFS loop
    while (!q.empty()) {
        pair<int, int> current_state = q.front();
        q.pop();

        int bob_curr = current_state.first;
        int alex_curr = current_state.second;
        int current_time = dist[bob_curr][alex_curr];

        // If we reached the destination (Bob at n, Alex at 1)
        if (bob_curr == n && alex_curr == 1) {
            final_k = current_time; // This is the shortest time
            break; // Found the shortest path, no need to explore further
        }

        // Iterate through all possible next moves for Bob
        for (int bob_next : adj[bob_curr]) {
            // Iterate through all possible next moves for Alex
            for (int alex_next : adj[alex_curr]) {
                // Collision check: they must not land on the same crossroads at the same time
                if (bob_next == alex_next) {
                    continue; // Collision, skip this pair of moves
                }

                // If this new combined state (bob_next, alex_next) hasn't been visited yet
                if (dist[bob_next][alex_next] == -1) {
                    dist[bob_next][alex_next] = current_time + 1; // Update distance
                    parent[bob_next][alex_next] = {bob_curr, alex_curr}; // Store parent for path reconstruction
                    q.push({bob_next, alex_next}); // Enqueue the new state
                }
            }
        }
    }

    // Output results
    if (final_k == -1) {
        cout << -1 << endl; // No valid routes found
    } else {
        cout << final_k << endl; // Print the minimum length of routes

        vector<int> bob_path;
        vector<int> alex_path;

        int curr_bob_pos = n; // Start reconstruction from Bob's destination
        int curr_alex_pos = 1; // Start reconstruction from Alex's destination

        // Backtrack using the parent array until we reach the starting state (1, n)
        while (curr_bob_pos != 1 || curr_alex_pos != n) {
            bob_path.push_back(curr_bob_pos);
            alex_path.push_back(curr_alex_pos);
            
            pair<int, int> prev_pos = parent[curr_bob_pos][curr_alex_pos];
            curr_bob_pos = prev_pos.first;
            curr_alex_pos = prev_pos.second;
        }
        // Add the initial positions to the paths
        bob_path.push_back(1);
        alex_path.push_back(n);

        // Paths were built in reverse order, so print them by iterating backwards
        for (int i = bob_path.size() - 1; i >= 0; --i) {
            cout << bob_path[i] << (i == 0 ? "" : " ");
        }
        cout << endl;

        for (int i = alex_path.size() - 1; i >= 0; --i) {
            cout << alex_path[i] << (i == 0 ? "" : " ");
        }
        cout << endl;
    }

    return 0;
}
  • Vote: I like it
  • -13
  • Vote: I do not like it