Submission #1329832

#TimeUsernameProblemLanguageResultExecution timeMemory
1329832model_codeSopsug (EGOI23_sopsug)C++20
100 / 100
322 ms57948 KiB
#include <bits/stdc++.h>
using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

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

    // parent[u] = v means good edge u->v exists, i.e. u sends garbage to v
    // In the final tree every node except root has out-degree 1 (edge toward root).
    vector<int> parent(n, -1); // parent from good edges
    vector<vector<int>> children(n); // reverse of good edges (for tree traversal)

    // Read good edges
    vector<pair<int,int>> good_edges(m);
    for(int i = 0; i < m; i++){
        int a, b;
        cin >> a >> b;
        good_edges[i] = {a, b};
        // edge a -> b means a sends to b, so parent[a] = b
        if(parent[a] != -1){
            // node a already has an outgoing good edge -> out-degree > 1 -> impossible
            cout << "NO\n";
            return 0;
        }
        parent[a] = b;
        children[b].push_back(a);
    }

    // Check good edges form a forest (no cycles)
    // Use union-find
    vector<int> uf(n);
    iota(uf.begin(), uf.end(), 0);
    vector<int> uf_rank(n, 0);
    function<int(int)> find = [&](int x) -> int {
        while(uf[x] != x) x = uf[x] = uf[uf[x]];
        return x;
    };
    auto unite = [&](int a, int b) -> bool {
        a = find(a); b = find(b);
        if(a == b) return false;
        if(uf_rank[a] < uf_rank[b]) swap(a, b);
        uf[b] = a;
        if(uf_rank[a] == uf_rank[b]) uf_rank[a]++;
        return true;
    };

    for(auto& [a, b] : good_edges){
        if(!unite(a, b)){
            // cycle detected
            cout << "NO\n";
            return 0;
        }
    }

    // Find root of each tree in the forest
    // root of tree containing node u: follow parent[] until -1
    // But this could be slow. Instead, roots are nodes with parent[u] == -1
    // that are the "top" of their tree. But we also need to know which tree
    // each node belongs to, and the root of that tree.

    // tree_root[u] = the root of the forest-tree containing u
    // We find it by following parent pointers up (iteratively to avoid stack overflow).
    vector<int> tree_root(n, -1);
    for(int i = 0; i < n; i++){
        if(tree_root[i] != -1) continue;
        // Follow parent chain to find root, collecting nodes along the way
        vector<int> path;
        int cur = i;
        while(cur != -1 && tree_root[cur] == -1){
            path.push_back(cur);
            if(parent[cur] == -1){
                // cur is a root
                tree_root[cur] = cur;
                break;
            }
            cur = parent[cur];
        }
        // Now cur either has tree_root set, or we reached a root
        int root_val = (cur != -1) ? tree_root[cur] : tree_root[path.back()];
        for(int x : path) tree_root[x] = root_val;
    }

    // Collect all tree roots
    vector<int> roots;
    for(int i = 0; i < n; i++){
        if(parent[i] == -1) roots.push_back(i);
    }

    // Read forbidden edges
    // forbidden: set of (u, v) meaning we cannot build edge u -> v
    unordered_set<long long> forbidden;
    auto encode = [&](int u, int v) -> long long {
        return (long long)u * n + v;
    };
    for(int i = 0; i < k; i++){
        int c, d;
        cin >> c >> d;
        forbidden.insert(encode(c, d));
    }

    // Also, good edges that already exist should be treated as taken,
    // and we should not create duplicate edges. But the problem says
    // "All of the M+K ordered pairs in the input will be distinct."
    // However, we also need to avoid self-loops and edges within the same tree
    // that would create cycles. Actually, the tree structure handles this:
    // we only add edges from tree roots to nodes in other trees.

    // Now we need to build a full arborescence.
    // Strategy from editorial:
    // - Consider the forest of good edges. We need to connect tree roots
    //   to nodes in other trees to form a single arborescence.
    // - An edge from tree_root r to some node v (in another tree) means
    //   r -> v (r sends garbage to v), making v's tree the parent.
    // - We use the Kosaraju-like trick:
    //   Phase 1: Run DFS from node 0's tree root. DFS visits nodes in the
    //   current tree (following children[] edges in reverse), and at each
    //   visited node, tries to connect unconnected tree roots.
    //   If not all trees connected, pick an unconnected tree root and continue.
    //   Remember the last starting tree root.
    //   Phase 2: Run DFS again from the last starting tree root (reset visited).
    //   If it connects everything, output the solution. Otherwise, NO.

    // Implementation details:
    // We maintain a set of "unconnected tree roots".
    // When we visit a node v during DFS, we try to connect each unconnected
    // tree root r to v: this means adding edge r -> v. We need:
    //   1. r != tree_root[v] (different tree)
    //   2. (r, v) not forbidden
    // If we succeed, we remove r from unconnected set and DFS into r's tree.

    // To efficiently iterate over unconnected roots and skip forbidden ones:
    // Keep unconnected roots in a set. For each visited node v, iterate
    // over unconnected roots and try to connect. If forbidden, skip.
    // The key insight from editorial: total work is O(N + K) because each
    // successful connection removes a root (O(N) total), and each failed
    // attempt corresponds to a forbidden edge (O(K) total).

    int num_trees = roots.size();
    if(num_trees == 1){
        // Already a single tree, just output good edges
        for(auto& [a, b] : good_edges){
            cout << a << " " << b << "\n";
        }
        return 0;
    }

    // We need to add num_trees - 1 new edges connecting tree roots to other nodes.
    // Plus the original M good edges.
    // Total should be N-1 edges.

    // DFS function: visits nodes within their tree (using children[] edges),
    // and at each node tries to connect unconnected tree roots.
    // Returns list of new edges added.

    auto solve = [&](int start_root) -> pair<bool, vector<pair<int,int>>> {
        // start_root is a tree root from which we begin
        vector<bool> visited(n, false);
        vector<pair<int,int>> new_edges;
        set<int> unconnected; // set of tree roots not yet connected
        for(int r : roots){
            if(r != start_root) unconnected.insert(r);
        }

        // DFS stack: we visit nodes and traverse children[] edges (within tree)
        // plus newly connected tree roots
        stack<int> stk;

        // Visit all nodes in start_root's tree first
        stk.push(start_root);
        visited[start_root] = true;

        while(!stk.empty()){
            int v = stk.top();
            stk.pop();

            // Try to connect unconnected tree roots to v
            // We iterate over unconnected and try edge (r, v)
            // Must skip if forbidden
            vector<int> to_connect;
            vector<int> still_unconnected;

            // We need to be careful: iterate and collect
            auto it = unconnected.begin();
            while(it != unconnected.end()){
                int r = *it;
                if(!forbidden.count(encode(r, v))){
                    to_connect.push_back(r);
                    it = unconnected.erase(it);
                } else {
                    ++it;
                }
            }

            for(int r : to_connect){
                new_edges.push_back({r, v});
                // Now DFS into r's tree
                stk.push(r);
                visited[r] = true;
            }

            // Also visit children of v within its own tree
            for(int c : children[v]){
                if(!visited[c]){
                    visited[c] = true;
                    stk.push(c);
                }
            }
        }

        if(unconnected.empty()){
            return {true, new_edges};
        }
        return {false, {}};
    };

    // Phase 1: Kosaraju-like approach
    // Run DFS from each unvisited tree root, tracking the last one
    {
        vector<bool> tree_visited(n, false); // which trees have been visited
        // We track visited by tree root
        set<int> unconnected_roots(roots.begin(), roots.end());

        int last_start = -1;

        // Start from root 0's tree root
        // Actually, start from roots[0]
        // We do the Kosaraju-like iteration:
        // Pick an unvisited tree root, DFS from it (connecting what we can),
        // repeat until all trees visited. The last starting root is the candidate.

        // But we need to be more careful. The editorial says:
        // Run DFS from v0=0. If not all visited, run from some unvisited v1, etc.
        // The last starting node vk is the candidate root.
        // Then verify by running DFS from vk again.

        // For Phase 1, we don't need to track new edges, just find the candidate.
        // Let's do a simplified version.

        vector<bool> visited(n, false);
        set<int> remaining_roots(roots.begin(), roots.end());

        // Start from the tree root of node 0
        int first = tree_root[0];
        last_start = first;
        remaining_roots.erase(first);

        // DFS from first
        stack<int> stk;
        stk.push(first);
        visited[first] = true;

        while(!remaining_roots.empty()){
            // Process DFS stack
            while(!stk.empty()){
                int v = stk.top();
                stk.pop();

                // Try to connect remaining roots to v
                auto it = remaining_roots.begin();
                while(it != remaining_roots.end()){
                    int r = *it;
                    if(!forbidden.count(encode(r, v))){
                        // Can connect r -> v
                        it = remaining_roots.erase(it);
                        stk.push(r);
                        visited[r] = true;
                    } else {
                        ++it;
                    }
                }

                // Visit children within tree
                for(int c : children[v]){
                    if(!visited[c]){
                        visited[c] = true;
                        stk.push(c);
                    }
                }
            }

            if(!remaining_roots.empty()){
                // Pick a remaining root and start DFS from it
                int r = *remaining_roots.begin();
                remaining_roots.erase(remaining_roots.begin());
                last_start = r;
                stk.push(r);
                visited[r] = true;
            }
        }

        // Phase 2: Try DFS from last_start
        auto [ok, new_edges] = solve(last_start);
        if(ok){
            for(auto& [a, b] : good_edges){
                cout << a << " " << b << "\n";
            }
            for(auto& [a, b] : new_edges){
                cout << a << " " << b << "\n";
            }
        } else {
            cout << "NO\n";
        }
    }

    return 0;
}
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...