Hướng dẫn giải của Phong tỏa


Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.

Nếu đường đi ~P~ được cho có bất kì chu trình vô hướng nào, đáp án là ~-1~. Trong tất cả các trường hợp còn lại, nếu ta bỏ toàn bộ các cạnh không thuộc ~P~, ta sẽ thu được ~P~ là đường đi duy nhất từ ~1~ tới ~n~, nên đáp án trong trường hợp này không thể là ~-1~. Ở dưới đây, ta giả sử đường đi ~P~ không tồn tại chu trình.

Gọi tập các đỉnh nằm trên đường đi ~P~ được cho là ~S~. Ta nhận thấy rằng điều kiện cần và đủ để ~P~ là đường đi duy nhất từ ~1~ tới ~n~ là nếu ta không thể đi từ bất kì đỉnh ~u~ nào trong ~S~ tới một đỉnh ~v~ trong ~S~ sử dụng các cạnh không nằm trong ~P~ (đỉnh ~u~ và ~v~ có thể giống nhau).

Subtask 1:

Xét đồ thị ~G'~ là đồ thị ~G~ nhưng tất cả các cạnh đều vô hướng. Ở subtask này, đồ thị ~G'~ được cho có duy nhất một chu trình do ~m = n~. Nếu chu trình này có bất kì đỉnh nào nằm trong ~S~, đáp án sẽ là ~1~, do ta có thể chọn một cạnh thuộc chu trình và xóa cạnh này sao cho chu trình biến mất. Trong trường hợp chu trình này không chứa đỉnh trong ~S~, đáp án sẽ là ~0~.

Subtask 2:

Ở subtask này, đường đi ~P~ là ~1 \to n~; ngoài ra, đề đảm bảo ~G~ không tồn tại chu trình chứa đỉnh ~1~ cũng như không tồn tại chu trình chứa đỉnh ~n~.

Xét ~G'~ là đồ thị ~G~ nhưng xóa cạnh thuộc ~P~. Ta giờ nhận thấy rằng bài toán đưa về việc xóa ít cạnh nhất khỏi ~G'~ để không còn đường đi từ ~1~ tới ~n~. Đây chính là bài lát cát cực tiểu, nên ta có thể dùng một thuật toán luồng cực đại bất kì để tìm đáp án (ở đây, đỉnh phát là ~1~ và đỉnh thu là ~n~).

Subtask 3:

Ta dựng đồ thị ~G'~ có ~n + 2~ đỉnh (~n~ đỉnh đánh số từ ~1~ tới ~n~, cùng với một đỉnh phát ~s~ và một đỉnh thu ~t~). Với các cạnh ~u \to v~ thuộc ~G~ không nằm trong ~P~, ta thêm một cạnh vào ~G'~ như sau:

  • Nếu ~u~ thuộc ~S~, cạnh được thêm vào sẽ đi ra từ ~s~; còn lại, cạnh này sẽ đi ra từ ~u~.

  • Nếu ~v~ thuộc ~S~, cạnh được thêm vào sẽ đi vào ~t~; còn lại, cạnh này sẽ đi vào ~v~.

Ta nhận thấy bất kì cách xóa cạnh thỏa mãn nào trên ~G~ sẽ tương ứng với một cách xóa cạnh khỏi ~G'~ để ~s~ không đến được ~t~. Vì thế, ta chỉ cần tìm lát cát cực tiểu trên ~G'~ để thu được đáp án.

Subtask 4:

Nhận thấy rằng mọi trọng số trên đồ thị ~G'~ trên đều bằng ~1~. Vì thế, áp dụng thuật toán Dinic (thay vì các thuật toán luồng cực đại khác như Edmond-Karps) sẽ thu được độ phức tạp là ~O(m \cdot min(n^{2/3}, m^{1/2}))~, đủ nhanh dể qua được subtask cuối cùng.

#include <bits/stdc++.h>

#include <algorithm>
#include <cassert>
#include <limits>
#include <queue>
#include <vector>


#include <vector>

namespace atcoder {

namespace internal {

template <class T> struct simple_queue {
    std::vector<T> payload;
    int pos = 0;
    void reserve(int n) { payload.reserve(n); }
    int size() const { return int(payload.size()) - pos; }
    bool empty() const { return pos == int(payload.size()); }
    void push(const T& t) { payload.push_back(t); }
    T& front() { return payload[pos]; }
    void clear() {
        payload.clear();
        pos = 0;
    }
    void pop() { pos++; }
};

}  // namespace internal

}  // namespace atcoder


namespace atcoder {

template <class Cap> struct mf_graph {
  public:
    mf_graph() : _n(0) {}
    explicit mf_graph(int n) : _n(n), g(n) {}

    int add_edge(int from, int to, Cap cap) {
        assert(0 <= from && from < _n);
        assert(0 <= to && to < _n);
        assert(0 <= cap);
        int m = int(pos.size());
        pos.push_back({from, int(g[from].size())});
        int from_id = int(g[from].size());
        int to_id = int(g[to].size());
        if (from == to) to_id++;
        g[from].push_back(_edge{to, to_id, cap});
        g[to].push_back(_edge{from, from_id, 0});
        return m;
    }

    struct edge {
        int from, to;
        Cap cap, flow;
    };

    edge get_edge(int i) {
        int m = int(pos.size());
        assert(0 <= i && i < m);
        auto _e = g[pos[i].first][pos[i].second];
        auto _re = g[_e.to][_e.rev];
        return edge{pos[i].first, _e.to, _e.cap + _re.cap, _re.cap};
    }
    std::vector<edge> edges() {
        int m = int(pos.size());
        std::vector<edge> result;
        for (int i = 0; i < m; i++) {
            result.push_back(get_edge(i));
        }
        return result;
    }
    void change_edge(int i, Cap new_cap, Cap new_flow) {
        int m = int(pos.size());
        assert(0 <= i && i < m);
        assert(0 <= new_flow && new_flow <= new_cap);
        auto& _e = g[pos[i].first][pos[i].second];
        auto& _re = g[_e.to][_e.rev];
        _e.cap = new_cap - new_flow;
        _re.cap = new_flow;
    }

    Cap flow(int s, int t) {
        return flow(s, t, std::numeric_limits<Cap>::max());
    }
    Cap flow(int s, int t, Cap flow_limit) {
        assert(0 <= s && s < _n);
        assert(0 <= t && t < _n);
        assert(s != t);

        std::vector<int> level(_n), iter(_n);
        internal::simple_queue<int> que;

        auto bfs = [&]() {
            std::fill(level.begin(), level.end(), -1);
            level[s] = 0;
            que.clear();
            que.push(s);
            while (!que.empty()) {
                int v = que.front();
                que.pop();
                for (auto e : g[v]) {
                    if (e.cap == 0 || level[e.to] >= 0) continue;
                    level[e.to] = level[v] + 1;
                    if (e.to == t) return;
                    que.push(e.to);
                }
            }
        };
        auto dfs = [&](auto self, int v, Cap up) {
            if (v == s) return up;
            Cap res = 0;
            int level_v = level[v];
            for (int& i = iter[v]; i < int(g[v].size()); i++) {
                _edge& e = g[v][i];
                if (level_v <= level[e.to] || g[e.to][e.rev].cap == 0) continue;
                Cap d =
                    self(self, e.to, std::min(up - res, g[e.to][e.rev].cap));
                if (d <= 0) continue;
                g[v][i].cap += d;
                g[e.to][e.rev].cap -= d;
                res += d;
                if (res == up) return res;
            }
            level[v] = _n;
            return res;
        };

        Cap flow = 0;
        while (flow < flow_limit) {
            bfs();
            if (level[t] == -1) break;
            std::fill(iter.begin(), iter.end(), 0);
            Cap f = dfs(dfs, t, flow_limit - flow);
            if (!f) break;
            flow += f;
        }
        return flow;
    }

    std::vector<bool> min_cut(int s) {
        std::vector<bool> visited(_n);
        internal::simple_queue<int> que;
        que.push(s);
        while (!que.empty()) {
            int p = que.front();
            que.pop();
            visited[p] = true;
            for (auto e : g[p]) {
                if (e.cap && !visited[e.to]) {
                    visited[e.to] = true;
                    que.push(e.to);
                }
            }
        }
        return visited;
    }

  private:
    int _n;
    struct _edge {
        int to, rev;
        Cap cap;
    };
    std::vector<std::pair<int, int>> pos;
    std::vector<std::vector<_edge>> g;
};

}  // namespace atcoder

using namespace std;
using namespace atcoder;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    int t; cin >> t;
    while (t--) {
        int n, m, k; cin >> n >> m >> k;
        vector<pair<int, int>> edg;
        for (int i = 0; i < m; i++) {
            int u, v; cin >> u >> v; u--; v--;
            edg.push_back({u, v});
        }
        vector<int> cnt(n); cnt[0] = cnt[n - 1] = 1;
        for (int i = 0; i < k; i++) {
            int id; cin >> id; id--;
            auto [u, v] = edg[id];
            cnt[u]++; cnt[v]++;
        }
        if (*max_element(cnt.begin(), cnt.end()) > 2) {
            // there is a cycle in P
            cout << -1 << '\n';
            continue;
        }

        mf_graph<int> mf(n + 2);
        int src = n, snk = n + 1;
        for (auto [u, v] : edg) {
            if (cnt[u] == 2) {
                u = src;
            }
            if (cnt[v] == 2) {
                v = snk;
            }
            mf.add_edge(u, v, 1);
        }
        cout << mf.flow(src, snk) - k << '\n';
    }
}

Bình luận

Hãy đọc nội quy trước khi bình luận.


Không có bình luận tại thời điểm này.