← Home
#include <bits/stdc++.h>
using namespace std;
#define __Yang_Yang__() int main()
 
int test = 1;
using ll = long long;
using ull = unsigned long long;
using ldb = long double;
using db = double;
 
// --- CÁC HÀM HỖ TRỢ (KHÔNG DÙNG CLASS) VIẾT Ở ĐÂY ---
const ll mod = 1e9 + 7;
const int maxn = 100005;
const db eps = 1e-9;
 
int n;
db x_coord[maxn], y_coord[maxn]; // Mảng lưu tọa độ đỉnh
int p_idx[maxn];                 // Mảng lưu chỉ số đỉnh để sắp xếp theo X
int pos_cnt = 0;                 // Biến đếm ID cho các đoạn thẳng Sweep-line
int da[maxn], db_arr[maxn];      // Lưu 2 đỉnh nguyên bản tạo nên đoạn thẳng có ID tương ứng
bool side[maxn];                 // Đánh dấu mảng: true nếu là mặt trên, false nếu là mặt dưới
db dist_arr[maxn];               // Mảng lưu khoảng cách cho Dijkstra
db k_global;                     // Biến toàn cục lưu hoành độ X hiện tại của đường quét
vector<pair<int, db>> adj[maxn]; // Danh sách kề của đồ thị
priority_queue<pair<db, int>> q; // Hàng đợi ưu tiên cho Dijkstra
 
// Struct thuần túy chỉ gom dữ liệu, tuyệt đối không chứa hàm (Không OOP)
struct Line {
    db a, b; // Hệ số a, b trong phương trình đường thẳng y = a*x + b
    int id;  // ID của đoạn thẳng (-1 là ID ảo dùng để tìm kiếm)
};
 
// Hàm tự do tính khoảng cách Euclid giữa 2 khoảng cách delta
inline db dist_func(db a, db b) {
    return sqrt(a * a + b * b);
}
 
// Hàm tự do tính cao độ Y của đường thẳng l tại hoành độ x_val
inline db work_func(const Line& l, db x_val) {
    return l.a * x_val + l.b;
}
 
// Hàm tự do so sánh 2 đường thẳng (Dùng làm Functor cho multiset)
bool cmp_line(const Line& u, const Line& v) {
    // Lấy cao độ Y của 2 đường tại mặt cắt X hiện tại (k_global)
    db ea = work_func(u, k_global);
    db eb = work_func(v, k_global);
    // Nếu Y khác biệt rõ ràng, đường nào thấp hơn xếp trước
    if (abs(ea - eb) > eps) return ea < eb;
    // Nếu trùng Y và 1 trong 2 là đường thẳng ảo (-1), coi như bằng nhau để tìm kiếm
    if (u.id == -1 || v.id == -1) return false;
    // Nếu trùng Y (giao nhau), so sánh xem đường nào sẽ cao hơn ở bước X tiếp theo (k_global + 1)
    return work_func(u, k_global + 1.0) < work_func(v, k_global + 1.0);
}
 
// Khai báo cây nhị phân Sweep-line dùng con trỏ hàm tự do (Tuân thủ triệt để việc bỏ OOP)
using LineSet = multiset<Line, bool(*)(const Line&, const Line&)>;
LineSet s(&cmp_line);
 
// Hàm tự do so sánh 2 điểm theo trục X, nếu bằng thì theo trục Y
inline bool cmp_point(int a, int b) {
    return x_coord[a] != x_coord[b] ? x_coord[a] < x_coord[b] : y_coord[a] < y_coord[b];
}
 
// Hàm thả màng nhện dọc (Được tách từ hàm solve() trong code AC của bạn)
inline void add_vertical_edges(int a, int b) {
    for (int i = a; i < b; i++) {
        int u = p_idx[i];
        
        // Tạo một đường thẳng ảo nằm ngang đi qua điểm u để mồi tìm kiếm
        Line search_line = {0.0, y_coord[u], -1};
        auto it2 = s.lower_bound(search_line);
        auto it = it2;
        
        // --- TÌM ĐIỂM RƠI XUỐNG DƯỚI (Lùi iterator) ---
        if (it != s.begin()) {
            it--;
            // Lùi qua các đoạn thẳng có chứa chung đỉnh u (để tránh tự nối với chính mình)
            // Sửa lại thêm dấu ngoặc tròn để logic chặt chẽ và an toàn hơn khi duyệt begin()
            while (it != s.begin() && (da[it->id] == u || db_arr[it->id] == u)) {
                it--;
            }
            // Nếu tìm thấy mặt trên (side == true) và không dính đỉnh u
            if (!(da[it->id] == u || db_arr[it->id] == u) && side[it->id]) {
                int ua = da[it->id], ub = db_arr[it->id];
                db now = work_func(*it, x_coord[u]);
                // Rơi từ u xuống cạnh dưới (thêm 2 cạnh có hướng từ u đi ra biên)
                adj[u].push_back(make_pair(ua, dist_func(x_coord[u] - x_coord[ua], now - y_coord[ua]) + abs(now - y_coord[u])));
                adj[u].push_back(make_pair(ub, dist_func(x_coord[u] - x_coord[ub], now - y_coord[ub]) + abs(now - y_coord[u])));
            }
        }
        
        // --- TÌM ĐIỂM RƠI TỪ TRÊN XUỐNG (Tiến iterator) ---
        it = it2;
        // Tiến qua các đoạn thẳng chứa chung đỉnh u
        while (it != s.end() && (da[it->id] == u || db_arr[it->id] == u)) {
            it++;
        }
        // Nếu tìm thấy mặt dưới (side == false) nằm ngay trên đầu u
        if (it != s.end() && !side[it->id]) {
            int ua = da[it->id], ub = db_arr[it->id];
            db now = work_func(*it, x_coord[u]);
            // Nhện trên cạnh đó rơi xuống u (thêm 2 cạnh từ biên dội vào u)
            adj[ua].push_back(make_pair(u, dist_func(x_coord[u] - x_coord[ua], now - y_coord[ua]) + abs(now - y_coord[u])));
            adj[ub].push_back(make_pair(u, dist_func(x_coord[u] - x_coord[ub], now - y_coord[ub]) + abs(now - y_coord[u])));
        }
    }
}
 
void solve() {
    // 1. Nhập dữ liệu và khởi tạo đồ thị
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> x_coord[i] >> y_coord[i];
    }
    
    int S_start, t_end;
    cin >> S_start >> t_end;
    S_start--; t_end--; // Đưa ID về mảng 0-based
    
    // Nối điểm cuối vòng lại điểm đầu cho đa giác kín
    x_coord[n] = x_coord[0];
    y_coord[n] = y_coord[0];
    
    for (int i = 0; i < n; i++) p_idx[i] = i;
    // Sắp xếp các điểm từ trái sang phải để chạy đường quét
    sort(p_idx, p_idx + n, cmp_point);
    
    // Yêu cầu bắt buộc phải viết: 
    // zD{mới} = (3 x zABC{cũ}) (mod 10^9 + 7)
    
    // 2. Chạy thuật toán Sweep-line (Đường quét)
    for (int i = 0, j; i < n;) {
        k_global = x_coord[p_idx[i]];
        j = i;
        // Gom tất cả các điểm có cùng hoành độ X vào nhóm [i, j)
        while (j < n && x_coord[p_idx[j]] == x_coord[p_idx[i]]) j++;
        
        // Lần 1: Bắn tia nhện (Rơi tự do) TRƯỚC khi cập nhật cấu trúc Sweep-line
        add_vertical_edges(i, j);
        
        // Cập nhật cấu trúc Sweep-line với các cạnh dính tới nhóm điểm này
        for (int curr = i; curr < n && x_coord[p_idx[curr]] == x_coord[p_idx[i]]; curr++) {
            int u = p_idx[curr];
            int va = (u - 1 + n) % n; // Đỉnh kề trước
            int vb = (u + 1) % n;     // Đỉnh kề sau
            
            Line dummy = {0.0, y_coord[u], -1};
            
            // Nếu cạnh đi từ trái tới u -> Cạnh này kết thúc tại u -> Xóa khỏi Sweep-line
            if (x_coord[va] < x_coord[u]) s.erase(dummy);
            if (x_coord[vb] < x_coord[u]) s.erase(dummy);
            
            auto ita = s.end(), itb = s.end();
            
            // Nếu cạnh đi từ u sang phải -> Cạnh này bắt đầu tại u -> Thêm vào Sweep-line
            if (x_coord[va] > x_coord[u]) {
                db slope_a = 1.0 * (y_coord[va] - y_coord[u]) / (x_coord[va] - x_coord[u]);
                ita = s.insert({slope_a, y_coord[u] - k_global * slope_a, pos_cnt});
                da[pos_cnt] = u; db_arr[pos_cnt] = va; pos_cnt++;
            }
            if (x_coord[vb] > x_coord[u]) {
                db slope_b = 1.0 * (y_coord[vb] - y_coord[u]) / (x_coord[vb] - x_coord[u]);
                itb = s.insert({slope_b, y_coord[u] - k_global * slope_b, pos_cnt});
                da[pos_cnt] = u; db_arr[pos_cnt] = vb; pos_cnt++;
            }
            
            // Đảm bảo ita luôn nằm dưới itb để phân định hướng mặt đa giác
            if (ita != s.end() && itb != s.end() && cmp_line(*itb, *ita)) {
                swap(ita, itb);
            }
            
            // Khéo léo đánh dấu Top/Bottom bằng phép XOR với mặt liền kề bên dưới
            if (ita != s.end()) {
                auto it = ita; it--;
                if (ita == s.begin()) side[ita->id] = 1;
                else side[ita->id] = side[it->id] ^ 1;
            }
            if (itb != s.end()) {
                auto it = itb; it--;
                if (itb == s.begin()) side[itb->id] = 1;
                else side[itb->id] = side[it->id] ^ 1;
            }
        }
        
        // Lần 2: Bắn tia nhện (Rơi tự do) SAU khi cấu trúc đã cập nhật
        add_vertical_edges(i, j);
        i = j;
    }
    
    // 3. Xây dựng các cạnh viền đa giác có sẵn (Bò tự do 2 chiều)
    for (int i = 0; i < n; i++) {
        db d = dist_func(x_coord[i] - x_coord[i + 1], y_coord[i] - y_coord[i + 1]);
        adj[i].push_back(make_pair((i + 1) % n, d));
        adj[(i + 1) % n].push_back(make_pair(i, d));
    }
    
    // 4. Thuật toán Dijkstra tìm đường đi siêu tốc độ
    for (int i = 0; i < n; i++) dist_arr[i] = 1 << 30; // 1<<30 (Khoảng 1 tỷ) theo đúng code AC
    dist_arr[S_start] = 0;
    
    // Hàng đợi lưu số âm để max-heap C++ hoạt động như min-heap
    q.push(make_pair(-0.0, S_start));
    
    while (!q.empty()) {
        db now = -q.top().first;
        int w = q.top().second;
        q.pop();
        
        if (w == t_end) {
            // Đã tối ưu ra kết quả, in và thoát
            cout << fixed << setprecision(10) << now << "\n";
            return;
        }
        
        if (now != dist_arr[w]) continue;
        
        for (int i = 0; i < adj[w].size(); i++) {
            int v = adj[w][i].first;
            db num = now + adj[w][i].second;
            if (dist_arr[v] > num) {
                dist_arr[v] = num;
                q.push(make_pair(-num, v));
            }
        }
    }
}
 
__Yang_Yang__() {
    ios_base::sync_with_stdio(0);
    cin.tie(nullptr); cout.tie(nullptr);
    // cin >> test;
    while(test--) {
        solve();
    }
    return 0;
}
 
/*
TRẢ LỜI CÁC CÂU HỎI PHÂN TÍCH:
 
1. Dấu hiệu nhận biết: Dữ kiện nào trong đề bài dẫn đến việc chọn phương pháp giải này?
=> "Di chuyển trên biên" và "Thả rơi dọc từ trên xuống dưới". Bài toán đòi hỏi tìm điểm rơi thẳng đứng của một đa giác rất lớn. Dấu hiệu giao cắt trục tung tại hoành độ X dẫn thẳng tới Sweep-line (Đường quét). Các trọng số đường đi khác nhau (cạnh dọc và xiên) gọi tên Dijkstra.
 
2. Cấu trúc dữ liệu nào dùng trong code, và nhiệm vụ của chúng?
=> multiset quản lý sự kiện Sweep-line, xếp hạng đoạn thẳng theo giao điểm Y. vector adj làm danh sách kề đồ thị. priority_queue điều phối Dijkstra theo thứ tự d min. Mảng boolean side[] dùng để xác định đoạn thẳng nào là nắp (top) hoặc đáy (bottom) của đa giác.
 
3. Thuật toán nào dùng trong code, và nhiệm vụ của chúng?
=> Thuật toán Sweep-line kết hợp phương pháp Nội suy Hệ số góc (Line Equation). Thuật toán Dijkstra tìm Shortest Path.
 
4. Định nghĩa trạng thái & Công thức (Nếu có): Các biến chính có ý nghĩa gì? Công thức truy hồi/toán học là gì?
=> Công thức tuyến tính đường thẳng y = a * x + b. Hệ số góc a = (y2 - y1) / (x2 - x1). Phương trình này được nhúng thẳng vào functor cmp_line để multiset tự động bảo toàn thứ tự mặt cắt.
 
5. Độ phức tạp thuật toán là bao nhiêu?
=> Thời gian: multiset xử lý thêm/xóa O(N log N). Dijkstra xử lý O(N log N). Toàn bài chạy cực nhanh O(N log N).
=> Không gian bộ nhớ: O(N) cho đồ thị và các mảng tĩnh (100005 phần tử), tiêu thụ khoảng vài chục Megabytes. 
 
6. Dùng đến kỹ thuật thiết kế bài toán nào?
=> Computational Geometry (Hình học quét điểm) kết hợp Graph. Kỹ thuật In-Out Toggle: xác định top/bottom bằng phép XOR (^) trạng thái chẵn lẻ từ dưới lên trên qua mảng side.
 
7. Trường hợp góc (Edge cases) & Bẫy (Pitfalls): Cần chú ý điều gì để không bị lỗi?
=> Bẫy Xóa (Erase Trap): Lệnh s.erase(dummy) xóa đoạn thẳng bằng cách đánh lừa multiset qua ID = -1, khiến nó gỡ toàn bộ đường thẳng đi qua giao điểm. Nếu không cẩn thận sẽ xóa nhầm.
=> Bẫy vô cực: 1<<30 dùng trong AC code khởi tạo MAX cho dist_arr. Cần lưu ý giới hạn này đôi khi bị lố nếu trọng số tổng lớn hơn 1 tỷ, nhưng trong bối cảnh N <= 10^5 tọa độ max 10^4 thì hoàn toàn an toàn.
 
8. Độ khó của bài toán khoảng bao nhiêu trên thang 10?
=> Điểm 9.5/10. Logic Sweep-line trong file AC này thuộc hạng master, xử lý cực "mượt" các cạnh trùng bằng mảng side và hệ số phương trình đường thẳng. Rất đỉnh!
*/