http://poj.openjudge.cn/practice/C15C?lang=en_US
n 点 m 边 k 天。
每条边在某一天会消失(仅仅那一天消失)。
问每一天有多少对点可以相互到达。
这个一看就是并查集。但是呢。。。。
“并查集是不能删边的”,这是这个问题的障碍。
并查集真的不能删边么?其实不是绝对的,如果删除的边是最后加入的边,并且并查集不进行路径压缩的话,自然是可以删除的。
P.S. 并查集找根如果路径压缩加按秩合并的话是 $O(log(log(n)))$,如果只按秩合并的话是 $O(log(n))$。
那么怎么让安排顺序,让并查集可以删除的边都是最后加入的边呢?这就用到了 cdq 分治(P.S. cdq 前辈提出的一类分治)。
考虑处理 1-10 天每天的连通对数目,并且最后将 1-10天的边都正确退出并查集的过程记做 cdq(1,10)。
那么 cdq(1, 10) 有如下的过程:
1. 6-10 的边加入并查集。
2. cdq(1, 5)
3. 6-10 的边退出并查集
4. 1-5 的边加入并查集
5. cdq(6, 10)
6. 1-5 的边退出并查集
注意:
0. 递归一直进行,直到 cdq(i, i) 的状况。这个时候就会发现,当前除了第 i 天的边,其他的边都加在并查集里。
1. 途中维护一个数 result,表示当前并查集中连通对的数目。如果是 cdq(i,i) 的话,其实什么都不用做,只要输出 result 就可以了。
2. 第 3 步删边的时候,应该反第 1 步的加边的顺序。为此,用一个栈存当前合并的集合根对。在 1 的时候记录栈顶位置 tmp,3 的时候反向退栈直到 tmp。 4 与 6 也是类似的。
3. 找根复杂度是 $log(n)$,cdq(l, r) 复杂度是 $O((r-l)log(n))$ 所以总复杂度应该是 $O(klog^2(n))$
另:
1. 一开始没注意顺序,瞎写。
2. Vjudge 说这个题 64 位整数的占位符是 %I64d %I64u,不要信它。
#include <stdio.h> #include <vector> #include <algorithm> using std::vector; const int MAXN = 100000 + 5; const int MAXM = 200000 + 5; int fa[MAXN]; int cnt[MAXN]; void initSets(int n) { for (int i = 1; i <= n; i++) { fa[i] = i; cnt[i] = 1; } } int root(int i) { for (; i - fa[i]; i = fa[i]); return i; } struct Edge { int x, y; }; vector<vector<Edge> > D; long long c2(int x) { return 1LL * x * (x - 1) / 2; } struct CDQ { std::pair<int, int> stk[MAXM]; int stkTop; long long result; void push(Edge& now) { int a = root(now.x); int b = root(now.y); if (a == b) return; result -= c2(cnt[a]); result -= c2(cnt[b]); if (cnt[a] > cnt[b]) std::swap(a, b); // a -> b cnt[b] += cnt[a]; result += c2(cnt[b]); fa[a] = b; stkTop++; stk[stkTop].first = a; stk[stkTop].second = b; } void push(int l, int r) { for (int i = l; i <= r; i++) for (int j = 0; j < D[i].size(); j++) push(D[i][j]); } void pull(int l, int r) { for (int i = r; i >= l; i--) { int s = stk[i].first; int r = stk[i].second; result -= c2(cnt[r]); cnt[r] -= cnt[s]; result += c2(cnt[s]); result += c2(cnt[r]); fa[s] = s; } } void cdq(int l, int r) { if (l == r) { printf("%lld\n", result); return; } int mid = (l + r) >> 1; int tmp = stkTop; push(mid + 1, r); cdq(l, mid); pull(tmp + 1, stkTop); stkTop = tmp; push(l, mid); cdq(mid + 1, r); pull(tmp + 1, stkTop); stkTop = tmp; } CDQ(int k, long long result) { stkTop = 0; this -> result = result; cdq(1, k); } }; int main() { int n, m, k; for (; scanf("%d %d %d", &n, &m, &k) == 3; ) { initSets(n); D.clear(); D.resize(k + 1); int d; Edge edge; long long result = 0; for (int i = 0; i < m; i++) { scanf("%d %d %d", &edge.x, &edge.y, &d); if (d <= k) { D[d].push_back(edge); continue; } int a = root(edge.x); int b = root(edge.y); if (cnt[a] > cnt[b]) std::swap(a, b); // a -> b fa[a] = b; result -= c2(cnt[a]) + c2(cnt[b]); cnt[b] += cnt[a]; result += c2(cnt[b]); } CDQ solve(k, result); } return 0; }
Open_POJ C15C Rabbit's Festival