按 Kruskal 的想法,按边权从小到大遍历这些边。两点 u,v 第一次连通时,当前遍历的边权 w 就是 f(u,v)。遍历每条边后,将这条边两个端点所在的连通块合并,用并查集维护图连通性(并查集维护无向图连通性其实是并查集的本质)。设将连通块 A,B 合并,则这条边对 A 中所有点的贡献为 w×∣B|,对 B 中所有点的贡献为 w×∣A∣。
由于需要记录连通块的具体元素,我们选择 并查集启发式合并 。 不妨设 ∣A∣⩾∣B∣,则 B 可以遍历,A 不能遍历。 并查集合并时将 B 中所有元素加入 A,可以证明复杂度是 O(nlogn)。并查集启发式合并 code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
vector<vector<int>> dsu(n); for (int i = 0; i < n; i++) { dsu[i].push_back(i); } auto uno = [&](int x, int y) { if (x = dsu[x][0], y = dsu[y][0]; x == y) { return; // 如果已经在同一个集合里,就不需要合并 } if (dsu[x].size() < dsu[y].size()) { swap(x, y); // 让小集合合并到大集合里 } for (auto z : dsu[y]) { // 不能写成 for (int i = 0; i < dsu[y].size(); i++) dsu[x].push_back(z); // 合并 dsu[z] = { x }; } };
对于 B,在合并时同时记录 B 中元素的答案。
对于 A,不遍历其中元素的前提下如何记录答案?
用 懒标记 LazyTag 记录 A 这个整体需要加上多少答案;
在集合 A 销毁(即合并到其它集合上)时,再将懒标记 LazyTag 分配给每个元素;
将另一个集合 B 合并到已有懒标记 LazyTag 的集合 A 时,需要给 B 的每个元素减去 A 已有的标记。
int N, n; cin >> N >> n; vector<int> color(N); for (int i = 0; i < N; i++) { cin >> color[i]; color[i]--; } vector<vector<int>> E(n); int m; cin >> m; constint B = ceil(sqrt(2 * m)); while (m--) { int u, v; cin >> u >> v; u--; v--; E[u].push_back(v); if (u != v) { // 注意自环 E[v].push_back(u); } } vector<vector<int>> G(n); for (int u = 0; u < n; u++) { for (auto v : E[u]) { if (E[v].size() > B) { G[u].push_back(v); // 所有点连大点 } } } vector<Z> val(n), lazy(n), dp(N); for (int x = 0; x < N; x++) { int u = color[x]; if (x == 0) { dp[x] = 1; } elseif (E[u].size() > B) { // 大点直接用 lazyTag dp[x] = lazy[u]; } elsefor (auto v : E[u]) { // 小点暴力更新 dp[x] += val[v]; } val[u] += dp[x]; for (auto v : G[u]) { // 把信息送给大点 lazy[v] += dp[x]; } } cout << dp.back() << endl;
intmain(){ #define ls (p<<1) #define rs (p<<1|1) #define mid ((L+R)>>1)
int n, m; cin >> n >> m;
vector<vector<pair<int, int>>> E(n << 2); while (m--) { int u, v, l, r; cin >> u >> v >> l >> r; u--, v--; l--; if (u > v) swap(u, v); auto add = [&](thisauto&& add, int p, int L, int R) -> void { if (l <= L && R <= r) { E[p].push_back({ u, v }); } elseif (l < R && L < r) { add(ls, L, mid), add(rs, mid, R); } }; add(1, 0, n); }
DSU dsu(n); auto solve = [&](thisauto&& solve, int p, int L, int R) -> void { int h = dsu.now(); for (auto [u, v] : E[p]) { dsu.uno(u, v); } if (R - L == 1) { dsu.lazy[dsu.find(0)] += L + 1; } else { solve(ls, L, mid), solve(rs, mid, R); } return dsu.undo(h); }; solve(1, 0, n);
ll res = 0; for (int i = 0; i < n; i++) { res ^= dsu.lazy[i]; } cout << res << endl;