1 条题解
-
0
思路与算法
把控制集合表示成以虚根0为根的祖先链。依编号建立新人的父亲为其全部受访对象的LCA;空对象集合直接连虚根。查询将给定点按DFS序排序去重,祖先链并集大小为Σdepth(v_i)-Σi>1 depth(LCA(v_{i-1},v_i))。
正确性说明
若此前每个控制集合为祖先链,则其交集正是各点LCA的祖先链,新人自身加入后接到LCA下方,归纳成立。空集合依公开样例不视为所有人。DFS序相邻链的交叠恰好覆盖此前并集与当前链的共有前缀,逐条加入的新增长度是深度差,因此公式准确。
复杂度
设输入对象总数B、查询元素总数S。建树和LCA O((N+B)logN),查询排序Σs logs、LCA O(SlogN),空间O(NlogN+最大查询大小),初始化包含祖先表。
易错点
虚根不是真人,深度从0计算。重复查询点先去重。不可按原图一般可达性求并集,本题是所有对象控制集合的交;父亲依更早编号在线确定。
参考代码(C++11)
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; int K = 1; while ((1 << K) <= n) K++; vector<vector<int>> up(K, vector<int>(n + 1)), g(n + 1); vector<int> dep(n + 1), tin(n + 1); auto lca = [&](int a, int b) { if (dep[a] < dep[b]) swap(a, b); for (int k = K - 1; k >= 0; k--) if ((dep[a] - dep[b]) >> k & 1) a = up[k][a]; if (a != b) { for (int k = K - 1; k >= 0; k--) if (up[k][a] != up[k][b]) { a = up[k][a]; b = up[k][b]; } a = up[0][a]; } return a; }; for (int i = 1; i <= n; i++) { int sz, p = -1; cin >> sz; while (sz--) { int v; cin >> v; p = p < 0 ? v : lca(p, v); } if (p < 0) p = 0; up[0][i] = p; dep[i] = dep[p] + 1; g[p].push_back(i); for (int k = 1; k < K; k++) up[k][i] = up[k - 1][up[k - 1][i]]; } vector<int> st(1, 0); int timer = 0; while (!st.empty()) { int u = st.back(); st.pop_back(); tin[u] = timer++; for (int v : g[u]) st.push_back(v); } int q; cin >> q; while (q--) { int k; cin >> k; vector<int> a(k); for (int &x : a) cin >> x; sort(a.begin(), a.end(), [&](int x, int y) { return tin[x] < tin[y]; }); a.erase(unique(a.begin(), a.end()), a.end()); long long ans = 0; for (int i = 0; i < (int)a.size(); i++) ans += dep[a[i]] - (i ? dep[lca(a[i - 1], a[i])] : 0); cout << ans << '\n'; } }
- 1