题目地址:
https://www.acwing.com/problem/content/description/354/
传说中的暗之连锁被人们称为Dark。Dark是人类内心的黑暗的产物,古今中外的勇者们都试图打倒它。经过研究,你发现Dark呈现无向图的结构,图中有 N N N个节点和两类边,一类边被称为主要边,而另一类被称为附加边。Dark有 N − 1 N-1 N−1条主要边,并且Dark的任意两个节点之间都存在一条只由主要边构成的路径。另外,Dark还有 M M M条附加边。你的任务是把Dark斩为不连通的两部分。一开始Dark的附加边都处于无敌状态,你只能选择一条主要边切断。一旦你切断了一条主要边,Dark就会进入防御模式,主要边会变为无敌的而附加边可以被切断。但是你的能力只能再切断Dark的一条附加边。现在你想要知道,一共有多少种方案可以击败Dark。注意,就算你第一步切断主要边之后就已经把Dark斩为两截,你也需要切断一条附加边才算击败了Dark。
输入格式:
第一行包含两个整数
N
N
N和
M
M
M。之后
N
–
1
N–1
N–1行,每行包括两个整数
A
A
A和
B
B
B,表示
A
A
A和
B
B
B之间有一条主要边。之后
M
M
M行以同样的格式给出附加边。
输出格式:
输出一个整数表示答案。
数据范围:
N
≤
100000
,
M
≤
200000
N≤100000,M≤200000
N≤100000,M≤200000,数据保证答案不超过
2
31
−
1
2^{31}−1
231−1
易知主要边构成了树结构,我们可以依次考虑每个附加边(每次只考虑一条附加边,当其它附加边不存在),然后考虑附加边和树边构成的环。对于这个环上的树边而言,删掉它们之中的任一条后,只需再将那条附加边删掉即可击败Dark。可以这样考虑,我们规定树边有一个权值,该权值初始为
0
0
0,每次枚举一个附加边的时候,都将其所在环的树边的权值加
1
1
1。那么在枚举完毕所有附加边之后,权值为
0
0
0的边是那种只要将其删去,就直接能击败Dark(因为它不在任何一个含附加边的环里),所以第一步删去它的方案数有
M
M
M个;权值为
1
1
1的边是那种将其删去后,还需要删掉
1
1
1条附加边就能击败Dark的边,所以第一步删去它的方案数有
1
1
1个;而权值大于
1
1
1的边是删去之无法击败Dark的边。所以问题转化为如何快速实现将树上的某条路径权值都加上某个数
c
c
c,和如何求出每条边最后的权值。这可以用树上差分的思想来做:
构造一个差分树,该树和原树的点集和边集一模一样。先对每个点
v
v
v开一个点权
p
[
v
]
p[v]
p[v],初始为
0
0
0,如果要将路径
x
↔
y
x\leftrightarrow y
x↔y这条树上路径的所有边权重都加上
c
c
c,那么我们可以将
p
[
x
]
p[x]
p[x]和
p
[
y
]
p[y]
p[y]都加上
c
c
c,并且
x
x
x和
y
y
y的最近公共祖先
u
u
u的点权
p
[
u
]
p[u]
p[u]减去
2
c
2c
2c。那么原树某条边的边权就等于差分树中该边指向的深度更深的节点的子树点权之和。代码如下:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010, M = 2 * N;
int n, m;
int h[N], e[M], ne[M], idx;
int depth[N], fa[N][17];
// d就是上文所说的点权
int d[N], q[N];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 初始化fa数组
void bfs() {
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[1] = 1;
int hh = 0, tt = 0;
q[tt++] = 1;
while (hh < tt) {
int t = q[hh++];
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (depth[j] > depth[t] + 1) {
depth[j] = depth[t] + 1;
fa[j][0] = t;
for (int k = 1; k < 17; k++)
fa[j][k] = fa[fa[j][k - 1]][k - 1];
q[tt++] = j;
}
}
}
}
// 最近公共祖先模板
int lca(int a, int b) {
if (depth[a] < depth[b]) swap(a, b);
for (int k = 16; k >= 0; k--)
if (depth[fa[a][k]] >= depth[b])
a = fa[a][k];
if (a == b) return a;
for (int k = 16; k >= 0; k--)
if (fa[a][k] != fa[b][k])
a = fa[a][k], b = fa[b][k];
return fa[a][0];
}
// 求以u为树根的子树点权之和,并返回
int dfs(int u, int father, int& res) {
// ans是以u为子树的点权,先将u的点权累加上去
int ans = d[u];
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j != father) {
// 计算u -> j这条边的边权
int t = dfs(j, u, res);
if (!t) res += m;
else if (t == 1) res++;
// 累加j的点权
ans += t;
}
}
return ans;
}
int main() {
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1; i++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
bfs();
for (int i = 0; i < m; i++) {
int a, b;
scanf("%d%d", &a, &b);
int p = lca(a, b);
d[a]++, d[b]++, d[p] -= 2;
}
int res = 0;
dfs(1, -1, res);
printf("%d\n", res);
return 0;
}
时间复杂度 O ( ( N + M ) log N ) O((N+M)\log N) O((N+M)logN),空间 O ( N log N ) O(N\log N) O(NlogN)。