闇の連鎖
闇の連鎖
传说中的暗之连锁被人们称为 Dark。
Dark 是人类内心的黑暗的产物,古今中外的勇者们都试图打倒它。
经过研究,你发现 Dark 呈现无向图的结构,图中有 $N$ 个节点和两类边,一类边被称为主要边,而另一类被称为附加边。
Dark 有 $N–1$ 条主要边,并且 Dark 的任意两个节点之间都存在一条只由主要边构成的路径。
另外,Dark 还有 $M$ 条附加边。
你的任务是把 Dark 斩为不连通的两部分。
一开始 Dark 的附加边都处于无敌状态,你只能选择一条主要边切断。
一旦你切断了一条主要边,Dark 就会进入防御模式,主要边会变为无敌的而附加边可以被切断。
但是你的能力只能再切断 Dark 的一条附加边。
现在你想要知道,一共有多少种方案可以击败 Dark。
注意,就算你第一步切断主要边之后就已经把 Dark 斩为两截,你也需要切断一条附加边才算击败了 Dark。
输入格式
第一行包含两个整数 $N$ 和 $M$。
之后 $N–1$ 行,每行包括两个整数 $A$ 和 $B$,表示 $A$ 和 $B$ 之间有一条主要边。
之后 $M$ 行以同样的格式给出附加边。
输出格式
输出一个整数表示答案。
数据范围
$N \leq 100000, \ M \leq 200000$,数据保证答案不超过 ${2}^{31}−1$。
输入样例:
4 1 1 2 2 3 1 4 3 4
输出样例:
3
解题思路
首先只考虑主要边那么就会构成一棵树,其中对于任意一条附加边如果将其添加到树中那么就会构成一个环。如下图,其中红色的是附加边:
那么如果要砍掉这个环上的一条主要边(即图中绿色的边),为了使得整个图不连通那么就要砍掉这条红色的边(附加边)。
现在再往图中加一条附加边:
对于新的附加边所构成的环,如果砍掉这个环上的一条标记了$1$的主要边,那么只有再砍掉这条新的附加边才能使得整个图不连通。
其中边上的数字$x$表示如果要砍掉这条主要边,那么还需要砍掉$x$条附加边才能使得整个图不连通。因此可以枚举每一条附加边,由该条附加边与主要边所构成的环上的每一条主要边都累加上$1$,表示砍掉这条主要边后要使得整个图不连通还要再砍该条附加边。
在枚举完所有的附加边后,再遍历一遍由主要边构成的树。对于树上的每一条边,假设这条边累加的数字为$x$,
- 如果$x=0$,则表示砍完这条边后不需要再砍任何附加边就可以使得整个图不连通,而由于必须要砍一条附加边,因此可以任选一条附加边砍掉,因此答案累加$m$。
- 如果$x=1$,则表示砍完这条边后还要再砍一条附加边才使得整个图不连通,那么只能砍掉这条附加边,只有一种方案,答案累加$1$。
- 如果$x>1$,则表示砍完这条边后还要再砍$x$条附加边才使得整个图不连通,而由于只能砍一条附加边,因此无解。
因此这题的核心问题就是如何快速的给树上某条路径上的边都加上一个数。可以类比一维差分,可以实现给某个连续区间都加上同一个数。在树上的话就是树上差分的问题。
对于一棵树每个节点都有权值$w_u$,那么节点$u$所对应的差分数组是$d_u = w_u - \sum\limits_{v \in \text{son}(u)}{w_v}$,即节点$u$的权值减去所有儿子的权值。那么如何通过差分数组得到每个节点原始(或者修改后)的权值呢?对于节点$u$,只需求出以$u$为根的整个子树关于差分数组的和。
这个可以用数学归纳法来证明,如果$u$是叶子节点,那么$d_u = w_u$,以$u$为根的整个子树关于差分数组的和就是$d_u$。现在假设$u$不是叶子节点,其子节点为$v \in \text{son}(u)$,根据归纳法以$v$为根的整个子树关于差分数组的和为$w_v$,那么以$u$为根的整个子树关于差分数组的和就是$d_u + \sum\limits_{v \in \text{son}(u)}{w_v} = w_u$,归纳假设成立。
先介绍点差分的概念。
如果要给树中从点$s$到$t$的路径上的每一个点都加上$c$,那么需要先求出$s$与$t$的最近公共祖先$\text{lca}$,同时$\text{lca}$的父节点为$p$,然后$$\begin{cases} d_s \ \ \ \mathrm{+}\mathrm{=} \ c \\ d_{\text{lca}} \ \mathrm{-}\mathrm{=} \ c \\ d_t \ \ \ \mathrm{+}\mathrm{=} \ c \\ d_{p} \ \ \ \mathrm{-}\mathrm{=} \ c \end{cases}$$
这里借用OI Wiki的图:
可以认为公式中的前两条是对蓝色方框内的路径进行操作,后两条是对红色方框内的路径进行操作。并且这样做可以保证最终只有$s$到$t$的路径上的点加上$c$。首先对于节点$p$,根据树上差分的定义,以$p$为根的整个子树关于差分数组的和加上$2c$又减去$2c$,因此不变,同理包括从$p$往上走的节点也是。而不包含$s$与$t$的子树自然也不会受到影响,因此只有路径上的点都被加了一次$c$。
然后是边差分。
我们知道树中除了根节点外每个节点都含有一个父节点,那么将从节点$u$到其父节点的这条边归属到节点$u$,因此树中的$n-1$条边就可以用除根节点的$n-1$个节点编号来表示了。因此$s$到$t$的路径上的每一条边都加上$c$,就可以转换成给对应的点加上$c$,这就变成了上面点差分的问题。
比如下图,$s$到$t$的路径上的边就分别对应标记了$1$的节点,因此对这些边加上$c$就等价于对标记了$1$的节点加上$c$:
$$\begin{cases} d_s \ \ \ \mathrm{+}\mathrm{=} \ c \\ d_t \ \ \ \mathrm{+}\mathrm{=} \ c \\ d_{\text{lca}} \ \mathrm{-}\mathrm{=} \ 2c \end{cases}$$
其中这题用到的是边差分。
AC代码如下,时间复杂度为$O(n + m \log{n})$:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 1e5 + 10, M = N * 2; 5 6 int n, m; 7 int head[N], e[M], ne[M], idx; 8 int fa[N][17], dep[N]; 9 int q[N], hh, tt = -1; 10 int d[N]; 11 12 void add(int v, int w) { 13 e[idx] = w, ne[idx] = head[v], head[v] = idx++; 14 } 15 16 int lca(int a, int b) { 17 if (dep[a] < dep[b]) swap(a, b); 18 for (int i = 16; i >= 0; i--) { 19 if (dep[fa[a][i]] >= dep[b]) a = fa[a][i]; 20 } 21 if (a == b) return a; 22 for (int i = 16; i >= 0; i--) { 23 if (fa[a][i] != fa[b][i]) a = fa[a][i], b = fa[b][i]; 24 } 25 return fa[a][0]; 26 } 27 28 int dfs(int u, int pre) { 29 int ret = 0; 30 for (int i = head[u]; i != -1; i = ne[i]) { 31 if (e[i] != pre) { 32 ret += dfs(e[i], u); 33 d[u] += d[e[i]]; 34 } 35 } 36 if (pre != -1) { 37 if (!d[u]) ret += m; 38 else if (d[u] == 1) ret++; 39 } 40 return ret; 41 } 42 43 int main() { 44 scanf("%d %d", &n, &m); 45 memset(head, -1, sizeof(head)); 46 for (int i = 0; i < n - 1; i++) { 47 int v, w; 48 scanf("%d %d", &v, &w); 49 add(v, w), add(w, v); 50 } 51 memset(dep, 0x3f, sizeof(dep)); 52 dep[0] = 0, dep[1] = 1; 53 q[++tt] = 1; 54 while (hh <= tt) { 55 int t = q[hh++]; 56 for (int i = head[t]; i != -1; i = ne[i]) { 57 if (dep[e[i]] > dep[t] + 1) { 58 dep[e[i]] = dep[t] + 1; 59 q[++tt] = e[i]; 60 fa[e[i]][0] = t; 61 for (int j = 1; j <= 16; j++) { 62 fa[e[i]][j] = fa[fa[e[i]][j - 1]][j - 1]; 63 } 64 } 65 } 66 } 67 for (int i = 0; i < m; i++) { 68 int a, b; 69 scanf("%d %d", &a, &b); 70 d[a]++, d[b]++, d[lca(a, b)] -= 2; 71 } 72 printf("%d", dfs(1, -1)); 73 74 return 0; 75 }
参考资料
AcWing 352. 闇の連鎖(算法提高课):https://www.acwing.com/video/571/
前缀和 & 差分 - OI Wiki:https://oi-wiki.org/basic/prefix-sum/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17360282.html