题意
有 $n$ 个点,初始没有连边,要求支持两个动态操作:
1. 加一条边(保证之前两点不连通)
2. 查询过一条边的简单路径数量(就是两边连通块的大小的乘积)
$n,Q\le 100000$
题解
由第一个操作保证之前两点不连通的性质可知,$n$ 个点最多被连成一棵 $n-1$ 条边的生成树。
离线做法:树剖+树状数组/线段树
我们可以离线确定出最终森林的形态(我们给森林中的每棵树随便定义个根)。
然后重新模拟操作时,只需要在最终的森林上做一些链操作(统计答案用)。
由于只有加边操作,所以模拟加边时,用并查集维护连通树及当前连通树的根。
当前连通树的根就是在加一些边后,一个点所在连通树的深度最小的点,这个深度根据最终这棵树的形态而定,越靠近根深度越小。
同时我们还要维护当前以每个点为根的子树的大小。
这样,插入一条边时,假设在最终的树形态中 $u$ 是 $v$ 的父亲(这个是已知的,因为我们随便定了一个根),那我们在合并两个连通树时,把 $v$ 所在并查集连向 $u$ 所在并查集。
由于并查集只维护连通树的根,我们要把从 $u$ 到其所在连通树的当前根的所有点都更新子树大小。
画个图
现在有一条边要连通如图两棵子树
那么所有橙色点(即u到根的路径)都要更新子树大小
根据最终树的形态来确定 $u$、$v$ 的合并方向,就是为了在维护连通性的同时维护连通树的根。之前说过,连通树的根是 在树的最终形态中深度最小的点,如果 $u$ 是 $v$ 的父亲,那 $u$ 的深度当然比 $v$ 小,应该让 $v$ 的并查集连向 $u$ 的。
这样,我们实际上就是一边加边、一边维护每个点的子树大小。
加边不用实际加,只要连并查集就行了,因为在加边时,只有从其一端点 到它所在连通树的根 上的所有点要修改,路径的两端点都已知,直接树链剖分就行了。
查询……那两个点的子树 $size$ 都已知了,而且一个点还是另一个点的父亲,(设 $x$ 是 $y$ 的父亲,$rt$ 是 $x,y$ 两点所在连通树的根)答案就是 $size_y\times (size_{rt}-size_y)$。
$size_y$ 就是 $y$ 这边的点数,$size_{rt}-size_y$ 就是 $x$ 这边的点数。
画个图就是
时间复杂度 $O(n + q\times log^2(n))$。
注:我没写这种方法,这是 $luogu$ 大佬的代码,其中的 $iota(f + 1, f + n + 1, 1);$ 就是 $for(int\space i=1;i<=n;++i)\space f[i]=i;$,即从 $1$ 开始以 $1$ 的斜率递增并依次赋值。
1 #include <cstdio> 2 3 #include <algorithm> 4 #include <numeric> 5 6 using namespace std; 7 8 struct edge { 9 int v; 10 edge* next; 11 }; 12 13 const int N = 100010; 14 15 int n; 16 char opt[N]; 17 int eu[N], ev[N], p[N], f[N], son[N], sum[N], fw[N], ord[N]; 18 int top[N]; 19 edge* g[N]; 20 21 int find(int x); 22 void add_edge(int u, int v); 23 void dfs1(int u); 24 void dfs2(int u); 25 void change(int k, int x); 26 void tchange(int u, int x); 27 int query(int k); 28 int low_bit(int k); 29 30 int main() { 31 int q; 32 scanf("%d%d", &n, &q); 33 for (int i = 1; i <= q; ++i) { 34 scanf(" %c%d%d", &opt[i], &eu[i], &ev[i]); 35 if (opt[i] == 'A') { 36 add_edge(eu[i], ev[i]); 37 add_edge(ev[i], eu[i]); 38 } 39 } 40 for (int u = 1; u <= n; ++u) 41 if (!p[u]) { 42 p[u] = -1; 43 dfs1(u); 44 } 45 for (int u = 1; u <= n; ++u) 46 if (p[u] == -1) { 47 top[u] = u; 48 dfs2(u); 49 } 50 iota(f + 1, f + n + 1, 1); 51 for (int i = 1; i <= n; ++i) { 52 tchange(i, 1); 53 tchange(p[i], -1); 54 } 55 for (int i = 1; i <= q; ++i) { 56 int u = eu[i], v = ev[i]; 57 if (p[u] == v) 58 swap(u, v); 59 int sv = query(ord[v]); 60 if (opt[i] == 'A') { 61 f[v] = find(u); 62 tchange(u, sv); 63 tchange(p[f[u]], -sv); 64 } else { 65 int s = query(ord[find(u)]); 66 printf("%d\n", (s - sv) * sv); 67 } 68 } 69 return 0; 70 } 71 72 void tchange(int u, int x) { 73 while (u != -1) { 74 change(ord[top[u]], x); 75 change(ord[u] + 1, -x); 76 u = p[top[u]]; 77 } 78 } 79 80 void dfs2(int u) { 81 static int t; 82 ord[u] = ++t; 83 if (!son[u]) 84 return; 85 top[son[u]] = top[u]; 86 dfs2(son[u]); 87 for (edge* q = g[u]; q; q = q->next) 88 if (p[q->v] == u && q->v != son[u]) { 89 top[q->v] = q->v; 90 dfs2(q->v); 91 } 92 } 93 94 int low_bit(int k) { 95 return k & -k; 96 } 97 98 void change(int k, int x) { 99 for (; k <= n; k += low_bit(k)) 100 fw[k] += x; 101 } 102 103 int query(int k) { 104 int ret = 0; 105 for (; k; k -= low_bit(k)) 106 ret += fw[k]; 107 return ret; 108 } 109 110 void dfs1(int u) { 111 sum[u] = 1; 112 for (edge* q = g[u]; q; q = q->next) 113 if (!p[q->v]) { 114 p[q->v] = u; 115 dfs1(q->v); 116 sum[u] += sum[q->v]; 117 if (sum[son[u]] < sum[q->v]) 118 son[u] = q->v; 119 } 120 } 121 122 int find(int x) { 123 return f[x] == x ? x : (f[x] = find(f[x])); 124 } 125 126 void add_edge(int u, int v) { 127 static edge pool[N * 2]; 128 static edge* p = pool; 129 p->v = v; 130 p->next = g[u]; 131 g[u] = p; 132 ++p; 133 }