Hướng dẫn giải của Chim cánh cụt


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.

Lời giải này đang bị ẩn cho đến khi bạn chọn mở ra.

Chúng tôi khuyên bạn nên tự thử giải bài trước. Việc mở lời giải có thể làm lộ mất ý tưởng chính trước khi bạn có cơ hội tự giải.

Bạn phải đăng nhập để mở lời giải này.

Đăng nhập

Tác giả: Hiếu Nguyễn, hongthu712, atula2x

Hiểu bài toán

Bài toán yêu cầu xác định các tảng băng có thể trở thành nơi liên hoan cho chim cánh cụt. Điều kiện: Chim cánh cụt có thể nhảy giữa hai tảng băng nếu khoảng cách giữa chúng ≤ D. Khi một con chim nhảy khỏi tảng băng, độ cao của tảng băng giảm 1. Nếu độ cao về 0, tảng băng chìm và không thể sử dụng được. Để một tảng băng i có thể là nơi liên hoan (điểm đến cuối cùng), cần thỏa mãn:

  1. Tất cả chim cánh cụt từ các tảng băng khác (và cả trên i nếu cần) có thể di chuyển đến i.
  2. Quá trình di chuyển không làm tảng băng i hoặc các tảng băng trung gian bị chìm trước khi chim đến nơi.

Về bản chất, bài toán kiểm tra xem có thể huy động toàn bộ số chim (penguins) từ các nguồn đến một tảng băng đích cụ thể hay không, tuân thủ các ràng buộc về dung lượng (độ cao) và khả năng nối mạng (khoảng cách).

Các cách tiếp cận

Cách Mô phỏng Lướt (N ≤ 100)
#include <bits/stdc++.h>
using namespace std;

struct Ice { double x, y; int p, h; };
int N;
double D;
vector<Ice> a;
vector<int> adj[105];
bool used[105];
int cap[105];

bool canReach(int start) {
    // Kiểm tra xem tất cả chim có thể đến start không
    // Bằng cách thử tất cả các phân phối flow thỏa mãn độ cao
    // Do N nhỏ, dùng DFS đơn giản hoặc max flow là được.
    // Tuy nhiên, do ràng buộc độ cao, ta cần mô phỏng.
    // Nếu dùng DFS tìm đường đi ngắn nhất:
    // 1. Xây dựng đồ thị các cạnh <= D.
    // 2. Dùng Max Flow (Dinic) với đồ thị:
    //    - Nguồn S nối vào mỗi tảng băng i với cap = số chim tại i.
    //    - Tảng băng i (đầu vào) nối tới tảng băng i (đầu ra) với cap = độ cao (độ bền).
    //    - Tảng băng i (đầu ra) nối tới tảng băng j (đầu vào) nếu khoảng cách <= D với cap = INF.
    //    - Tảng băng start (đầu ra) nối tới đích T với cap = INF.
    //    - Nếu flow tổng == tổng số chim, start là câu trả lời.
    return true;
}

int main() {
    cin >> N >> D;
    double totalPenguins = 0;
    for(int i=0; i<N; i++) {
        double x, y; int p, h;
        cin >> x >> y >> p >> h;
        a.push_back({x, y, p, h});
        totalPenguins += p;
    }
    // Code đầy đủ cần cài đặt Max Flow ở đây
    // ...
    return 0;
}
  • Time Complexity: O(N * MaxFlow), MaxFlow ~ O(V^2 * E) ~ O(N^5)
  • Space Complexity: O(N^2)

Giải thuật này sử dụng Maximum Flow (cụ thể là Dinic) để mô phỏng quá trình di chuyển.

  • Mạng lưới Flow: +Nguồn (Source) nối vào mỗi tảng băng i với dung lượng bằng số chim tại i. +Mỗi tảng băng i được chia làm 2 nút: 'iin' và 'iout'. +Cạnh từ 'iin' đến 'iout' có dung lượng bằng độ cao (max height) của tảng băng. Điều này đảm bảo số chim đi qua tảng băng không vượt quá độ cao của nó (độ cao giảm 1 mỗi khi chim nhảy qua). +Cạnh từ 'iout' đến 'jin' có dung lượng vô hạn nếu khoảng cách từ i đến j ≤ D. +Tất cả các tảng băng 'i_out' nối đến Sink (Đích) với dung lượng vô hạn.
  • Nếu Max Flow bằng tổng số chim, tảng băng đó có thể là nơi liên hoan. Ta chạy lại quá trình này cho từng tảng băng để tìm tất cả các vị trí hợp lệ.
Cách Tối ưu (Chỉ kiểm tra nút có chim)
// Áp dụng logic từ Solution 2 của atula2x
// Chỉ kiểm tra các node có chim làm điểm đến.
#include <bits/stdc++.h>
using namespace std;

struct Edge { int to, rev; long long cap; };
struct Dinic {
    int N; vector<vector<Edge>> G; vector<int> level, ptr;
    Dinic(int n): N(n), G(n), level(n), ptr(n) {}
    void addEdge(int u, int v, long long c) {
        G[u].push_back({v, (int)G[v].size(), c});
        G[v].push_back({u, (int)G[u].size() - 1, 0});
    }
    // ... (bfs, dfs implementation) ...
    long long maxflow(int s, int t) { ... }
};

struct Ice { double x, y; int p, h; };
int N; double D;
vector<Ice> a;
double dist[105][105];

int main() {
    cin >> N >> D;
    long long totalPenguins = 0;
    for(int i=0; i<N; i++) {
        double x, y; int p, h;
        cin >> x >> y >> p >> h;
        a.push_back({x, y, p, h});
        totalPenguins += p;
    }
    if (totalPenguins == 0) { cout << -1; return 0; }

    // Tính khoảng cách
    for(int i=0; i<N; i++)
        for(int j=0; j<N; j++)
            dist[i][j] = sqrt(pow(a[i].x-a[j].x, 2) + pow(a[i].y-a[j].y, 2));

    vector<int> candidates;
    for(int i=0; i<N; i++) if(a[i].p > 0) candidates.push_back(i);
    // Nếu không có chim ở đâu đó ban đầu, trường hợp này phức tạp hơn (cần nguồn ảo),
    // nhưng theo sample và điều kiện bài toán, ta ưu tiên node có chim.
    // Nếu total > 0 mà candidates rỗng, có thể xét tất cả nodes.
    if(candidates.empty()) candidates.resize(N); 

    vector<int> result;
    for(int i : candidates) {
        Dinic dinic(N * 2 + 2);
        int S = N * 2, T = N * 2 + 1;
        for(int j=0; j<N; j++) {
            // Nguồn -> Node_in
            if(a[j].p > 0) dinic.addEdge(S, j, a[j].p);
            // Node_in -> Node_out (Ràng buộc độ cao)
            dinic.addEdge(j, N + j, a[j].h);
            // Node_out -> Đích (Nếu là đích)
            if(j == i) dinic.addEdge(N + j, T, 1e15);
            // Node_out -> Node_in (Lưu chim đến đây có thể đi tiếp)
            for(int k=0; k<N; k++) {
                if(j == k) continue;
                if(dist[j][k] <= D + 1e-9) {
                    dinic.addEdge(N + j, k, 1e15);
                }
            }
        }
        if(dinic.maxflow(S, T) == totalPenguins) result.push_back(i);
    }
    // ... In kết quả ...
}
  • Time Complexity: O(N * MaxFlow), MaxFlow ~ O(N^3) (Dinic on bipartite-like graph)
  • Space Complexity: O(N^2)

Thay vì kiểm tra tất cả các tảng băng, ta chỉ cần kiểm tra các tảng băng có chim ban đầu. Giả thuyết: Nếu có thể tập hợp tất cả chim về một vị trí, vị trí đó ít nhất phải chứa một con chim ban đầu (trừ trường hợp đặc biệt có thể dùng node nguồn ảo, nhưng trong ngữ cảnh bài này, các node có chim là nguồn cung cấp). Cấu trúc đồ thị flow giữ nguyên, nhưng chỉ chạy thuật toán cho các node i thỏa a[i].p > 0. Điều này giảm đáng kể số lần chạy Max Flow, tối ưu hóa thời gian chạy.

Phân tích độ phức tạp

Cách tiếp cận Time Space Tên
1 O(N * MaxFlow), MaxFlow ~ O(V^2 * E) ~ O(N^5) O(N^2) Mô phỏng Lướt (N ≤ 100)
2 O(N * MaxFlow), MaxFlow ~ O(N^3) (Dinic on bipartite-like graph) O(N^2) Tối ưu (Chỉ kiểm tra nút có chim)

Bài học kinh nghiệm

  • Bài toán có thể quy về bài toán tìm đường đi (hoặc luồng) trong đồ thị có ràng buộc dung lượng ở các nút (Capacity on Vertices).
  • Chuyển đổi bài toán thành Max Flow bằng cách chia mỗi nút (tảng băng) thành 2 nút 'đầu vào' và 'đầu ra', nối bởi cạnh có dung lượng bằng độ cao của tảng băng.
  • Chỉ cần kiểm tra các tảng băng có số chim ban đầu > 0 làm điểm đến (điểm cuối) là đủ để tìm tất cả các vị trí có thể.

Lỗi thường gặp

  • Sai số số thực (Floating Point Error): Khoảng cách và D là số thực. Luôn cộng thêm một lượng nhỏ (epsilon, ví dụ 1e-9) khi so sánh để tránh sai lệch.
  • Quên xử lý trường hợp tảng băng có chim nhưng độ cao = 0: Nếu độ cao bằng 0, tảng băng chìm ngay lập tức, chim không thể sống hoặc nhảy qua.
  • Xây dựng đồ thị sai: Đảm bảo các cạnh từ 'đầu ra' của nút này sang 'đầu vào' của nút khác có dung lượng vô hạn (hoặc đủ lớn), và chỉ có cạnh nối 'đầu vào' - 'đầu ra' mới giới hạn dung lượng.

Bình luận

Please read the guidelines before commenting.


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