Submission #1295886

#TimeUsernameProblemLanguageResultExecution timeMemory
1295886Neco_arcJail (JOI22_jail)C++17
0 / 100
3 ms3144 KiB
#include <iostream> #include <vector> #include <algorithm> #include <queue> using namespace std; // Cấu hình giới hạn const int MAXN = 120005; const int LOG = 18; // 2^17 > 120000 const int MAX_NODES = MAXN * LOG * 2 + MAXN + 50000; const int MAX_EDGES = MAX_NODES * 4; // Biến toàn cục cho Đồ thị ban đầu (Cây) vector<int> adj[MAXN]; int up[MAXN][LOG]; int depth[MAXN]; int N, M; // Biến toàn cục cho Đồ thị phụ thuộc (Virtual Graph) // Dùng mảng tĩnh để tiết kiệm bộ nhớ và thời gian (Thay cho vector<vector>) int head[MAX_NODES], nxt[MAX_EDGES], to[MAX_EDGES]; int deg[MAX_NODES]; int edge_cnt = 0; int total_nodes = 0; void add_edge(int u, int v) { to[++edge_cnt] = v; nxt[edge_cnt] = head[u]; head[u] = edge_cnt; deg[v]++; } // DFS để dựng Binary Lifting void dfs(int u, int p, int d) { depth[u] = d; up[u][0] = p; for (int k = 1; k < LOG; ++k) { up[u][k] = up[up[u][k-1]][k-1]; } for (int v : adj[u]) { if (v != p) dfs(v, u, d + 1); } } int get_lca(int u, int v) { if (depth[u] < depth[v]) swap(u, v); for (int k = LOG - 1; k >= 0; --k) { if (depth[u] - (1 << k) >= depth[v]) u = up[u][k]; } if (u == v) return u; for (int k = LOG - 1; k >= 0; --k) { if (up[u][k] != up[v][k]) { u = up[u][k]; v = up[v][k]; } } return up[u][0]; } // Hàm lấy ID node ảo // id: 1..M (Prisoners) // SRC: M + (u-1)*LOG + k + 1 // SNK: M + N*LOG + (u-1)*LOG + k + 1 inline int ID_SRC(int u, int k) { return M + (u - 1) * LOG + k + 1; } inline int ID_SNK(int u, int k) { return M + N * LOG + (u - 1) * LOG + k + 1; } // Nối các đoạn phủ đường đi u -> ancestor vào tù nhân id // Type 0: Source (Segment -> id), Type 1: Sink (id -> Segment) void connect_path(int u, int ancestor, int id, int type) { int d = depth[u] - depth[ancestor]; // Số bước cần nhảy for (int k = LOG - 1; k >= 0; --k) { if ((d >> k) & 1) { // Đoạn (u, 2^k) if (type == 0) add_edge(ID_SRC(u, k), id); else add_edge(id, ID_SNK(u, k)); u = up[u][k]; } } } void solve() { if (!(cin >> N)) return; // Reset dữ liệu cây for (int i = 1; i <= N; ++i) adj[i].clear(); for (int i = 0; i < N - 1; ++i) { int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); } dfs(1, 1, 0); cin >> M; vector<pair<int, int>> prisoners(M + 1); // Reset đồ thị ảo total_nodes = M + 2 * N * LOG; edge_cnt = 0; // Fill mảng head và deg cẩn thận // Vì total_nodes khá lớn, dùng vòng lặp reset thay vì memset toàn bộ mảng MAX for(int i=0; i<=total_nodes; ++i) { head[i] = 0; deg[i] = 0; } // Xây dựng khung Doubling for (int u = 1; u <= N; ++u) { for (int k = 1; k < LOG; ++k) { // Source: Con trỏ đến Cha (Gộp dòng chảy) int src_curr = ID_SRC(u, k); int src_prev = ID_SRC(u, k-1); int src_jump = ID_SRC(up[u][k-1], k-1); add_edge(src_prev, src_curr); add_edge(src_jump, src_curr); // Sink: Cha trỏ đến Con (Phân tán dòng chảy) int snk_curr = ID_SNK(u, k); int snk_prev = ID_SNK(u, k-1); int snk_jump = ID_SNK(up[u][k-1], k-1); add_edge(snk_curr, snk_prev); add_edge(snk_curr, snk_jump); } } for (int i = 1; i <= M; ++i) { int s, t; cin >> s >> t; prisoners[i] = {s, t}; // Liên kết tù nhân với vị trí cụ thể (k=0) add_edge(i, ID_SRC(s, 0)); // i đến từ S_i add_edge(ID_SNK(t, 0), i); // i đi đến T_i int lca = get_lca(s, t); // Xử lý Source (Ai đang chắn đường i?) -> Query path(S -> T) trừ S // Nhánh S -> LCA (trừ S): Nhảy lên 1 bước từ S rồi query if (s != lca) { connect_path(up[s][0], lca, i, 0); // S->LCA hướng lên add_edge(ID_SRC(lca, 0), i); // Thêm đỉnh LCA } // Nhánh T -> LCA (trừ LCA): Vì đây là đường đi của i, // và Source check là "Ai xuất phát trên đường này". // Đường đi là S->...->LCA->...->T. // Phần T->LCA thực chất là query các node từ T ngược về con của LCA. // Logic connect_path luôn query đường lên (node -> ancestor). // Đường từ LCA xuống T cũng là tập hợp các node trên đường thẳng đứng từ T lên LCA. // Ta query từ T lên con của LCA. if (t != lca) { // Query T lên LCA, nhưng LCA đã add ở trên, nên chỉ lên đến con của LCA? // Không, connect_path(T, LCA, ...) sẽ bao gồm cả LCA. // Cần cẩn thận không add trùng LCA 2 lần nếu không cần thiết (dù add trùng ko sao). // Nhưng quan trọng: path Source cover toàn bộ node trên S->T ngoại trừ S. // connect_path(T, LCA, i, 0) sẽ cover từ T lên LCA. // LCA đã được cover ở nhánh S. // Để đơn giản và tránh self-loop i->i, ta cần loại bỏ S. S nằm ở nhánh 1. // Nhánh 2 là T->LCA. Không chứa S. An toàn. connect_path(t, lca, i, 0); // Lưu ý: connect_path(u, anc) bao gồm cả u và anc. // Vậy LCA được add 2 lần. Không sao. } else { // Nếu S == LCA, đường đi là dốc xuống S -> T. // Nhánh 1 rỗng (vì bỏ S). // Nhánh 2 là T -> S. Cover path từ T lên S. // Vì bỏ S (là điểm xuất phát), ta dùng connect_path(T, S, ...) nhưng bỏ S? // connect_path phủ từ T lên S. Ta chỉ cần dừng trước S. // Nhưng hàm connect_path của mình phủ cả ancestor. // Hack nhẹ: tách node LCA ra xử lý riêng hoặc chấp nhận query dư (nhưng phải bỏ S). // Cách xử lý chính xác để BỎ S và BỎ T: // Path S -> T. // SOURCE: Cần cover (S_next -> T). // SINK: Cần cover (S -> T_prev). } } // Logic lại phần Connect Path chuẩn xác để tránh Self-Loop // Reset edge cho chắc và viết lại đoạn loop trên // (Đoạn dưới đây thay thế đoạn loop i=1..M ở trên) } // Hàm giải quyết chính xác logic path void solve_exact() { if (!(cin >> N)) return; for (int i = 1; i <= N; ++i) adj[i].clear(); for (int i = 0; i < N - 1; ++i) { int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); } dfs(1, 1, 0); cin >> M; total_nodes = M + 2 * N * LOG; edge_cnt = 0; for(int i=0; i<=total_nodes; ++i) { head[i] = 0; deg[i] = 0; } for (int u = 1; u <= N; ++u) { for (int k = 1; k < LOG; ++k) { add_edge(ID_SRC(u, k-1), ID_SRC(u, k)); add_edge(ID_SRC(up[u][k-1], k-1), ID_SRC(u, k)); add_edge(ID_SNK(u, k), ID_SNK(u, k-1)); add_edge(ID_SNK(u, k), ID_SNK(up[u][k-1], k-1)); } } for(int i=1; i<=M; ++i){ int s, t; cin >> s >> t; // Liên kết cơ bản add_edge(i, ID_SRC(s, 0)); add_edge(ID_SNK(t, 0), i); int lca = get_lca(s, t); // 1. SOURCE Constraints (j -> i): Path S->T excluding S // Nhánh S -> LCA: Bắt đầu từ up[s][0] (để bỏ S) lên tới LCA if(s != lca) connect_path(up[s][0], lca, i, 0); // Nhánh T -> LCA: Từ T lên tới child của LCA (để tránh LCA nếu đã add, hoặc add cả LCA cũng dc) // Tuy nhiên, path từ S->T đi qua LCA. LCA không phải là S (trừ khi S=LCA). // Nếu S!=LCA, LCA đã được add ở dòng trên. // Nếu S==LCA, dòng trên không chạy. Ta cần add LCA ở dòng dưới? Không, LCA là S, phải bỏ S. // Vậy: // Nếu S != LCA: Add path(up[S], LCA). // Nhánh T: Add path(T, LCA) nhưng bỏ LCA (để tránh trùng). // Hoặc đơn giản: Add path(T, LCA). LCA bị tính 2 lần không sao. // NHƯNG nếu S=LCA, path(T, LCA) sẽ chứa LCA=S -> Sai (phải bỏ S). // Fix: // Path 1 (S hướng lên): nếu S!=LCA, connect(up[S], LCA). (Bao gồm LCA). // Path 2 (T hướng lên): connect(T, ...). // Điểm gặp nhau là LCA. // Nếu S == LCA: Cần cover toàn bộ T -> LCA ngoại trừ LCA. => connect(T, child_of_LCA_on_T_branch?). // Khó tìm child. // Đơn giản hơn: connect(T, LCA). Nếu S==LCA thì không add cạnh nối từ node ảo LCA vào i? // Không được, node ảo LCA chứa nhiều người khác. // GIẢI PHÁP TỐI ƯU: Sử dụng hàm connect_path linh hoạt (exclude ancestor) // Add SOURCE if (s != lca) { connect_path(up[s][0], lca, i, 0); // Include LCA } // Nhánh bên T: đi từ T lên LCA. // Nếu S == LCA, ta phải bỏ LCA. -> connect(T, strict_child_of_LCA). // Nếu S != LCA, ta có thể lấy cả LCA (trùng k sao). // Để tổng quát: Luôn lấy T -> LCA. // TRỪ TRƯỜNG HỢP S==LCA thì phải xử lý LCA riêng (không add). // Nhưng connect_path lại add theo đoạn. // Cách dễ nhất: Add T -> LCA. // Nếu S == LCA, ta xóa cạnh SRC(LCA, 0) -> i ? Không thể. // Ta dùng ID_SRC(u, k) -> i. // S==LCA nghĩa là i xuất phát tại LCA. // Cạnh i -> SRC(LCA, 0) đã có. // Nếu ta add SRC(LCA, ...) -> i, ta tạo chu trình. // Vậy khi add Source path cho i, ta TUYỆT ĐỐI không được đụng vào node ảo chứa S. // Node ảo (u, k) chứa S nếu u == S hoặc u là con cháu S... sai. // Node ảo (u, k) đại diện cho đoạn từ u ngược lên 2^k. // Nó chứa S nếu S nằm trong đoạn đó. // Chốt: // Nhánh S -> LCA: Đi từ up[s][0] lên LCA. (An toàn, vì đoạn này nằm trên S hẳn). // Nhánh T -> LCA: Đi từ T lên LCA. // Đoạn này chứa LCA. // Nếu S == LCA, đoạn này chứa S -> tạo chu trình. // Vậy nếu S == LCA, ta chỉ được đi từ T lên đến "con của LCA". // Để làm điều này, ta nhảy T lên đến khi depth = depth[LCA] + 1. if (depth[t] > depth[lca]) { int t_jump = t; // Nhảy t_jump lên sát LCA for(int k=LOG-1; k>=0; --k){ if(depth[t_jump] - (1<<k) > depth[lca]) t_jump = up[t_jump][k]; } // t_jump bây giờ là con trực tiếp của LCA connect_path(t, t_jump, i, 0); // Add từ T lên sát LCA // Còn bản thân LCA? // Nếu S != LCA, ta cần add LCA. (Đã add ở nhánh S->LCA). // Nếu S == LCA, ta không add LCA. // Logic này đúng hoàn toàn! } // 2. SINK Constraints (i -> j): Path S->T excluding T // Tương tự, ta cần bỏ T. // Nhánh S -> LCA: Đi từ S lên LCA. // Nếu T == LCA, phải bỏ LCA. -> Chỉ đi S lên sát LCA. if (depth[s] > depth[lca]) { int s_jump = s; for(int k=LOG-1; k>=0; --k){ if(depth[s_jump] - (1<<k) > depth[lca]) s_jump = up[s_jump][k]; } connect_path(s, s_jump, i, 1); } // Nhánh T -> LCA: Bỏ T, tức là đi từ up[T][0] lên LCA. // Nếu T != LCA: if (t != lca) { connect_path(up[t][0], lca, i, 1); // Include LCA } // Nếu T == LCA, nhánh này rỗng (đúng). Và nhánh S->LCA ở trên đã bỏ LCA (đúng). } // Topo Sort queue<int> q; for(int i=1; i<=total_nodes; ++i) { if(deg[i] == 0) q.push(i); } int processed = 0; while(!q.empty()){ int u = q.front(); q.pop(); processed++; int e = head[u]; while(e){ int v = to[e]; deg[v]--; if(deg[v] == 0) q.push(v); e = nxt[e]; } } if(processed == total_nodes) cout << "Yes\n"; else cout << "No\n"; } int main() { ios_base::sync_with_stdio(false); cin.tie(NULL); int Q; if (cin >> Q) { while (Q--) solve_exact(); } 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...