​题目传送门​

分析:

首先梳理下题意,一个无向图中,有\(N - 1\)条主要边,这么多主要边构成了原图的一棵支撑树。同时还有\(M\)条附加边,每条附加边都会增加一个回路。我们需要做的就是将原图斩为两个不连通的部分并且需要切断两条边,第一条边必须是主要边,第二条边必须是附加边

AcWing 352 . 闇の連鎖_深度遍历

如上图所示,黑色线条连接的边就是主要边,黄色线条连接的边就是附加边。

我们考察所有树边,如果切掉这条边的话,可以切哪些非树边,使得最后图不连通。

  • 如果这条树边在一个环中,那么只需要把构成这个环的那条非树边切掉就行。
  • 如果这条树边在两个环中,那么就需要切掉两条非树边。
  • 如果不在环中,那么不需要切非树边,即随意切一条非树边即可。

那么如何统计一条树边,在几个环中呢?对每一条非树边\((u,v)\),对\(u\),\(v\)树上路径上的所有边\(+1\),最后统计每条边被加了多少次即可。这一操作可以通过树上差分来进行。

#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = 200010;
int depth[N], f[N][16];
int n, m, d[N];
int ans;
//邻接表
int e[M], h[N], idx, ne[M];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
//树上倍增
void bfs(int t) {
queue<int> q;
q.push(t);
depth[t] = 1;
while (q.size()) {
int u = q.front();
q.pop();
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (!depth[j]) {
depth[j] = depth[u] + 1;
q.push(j);
f[j][0] = u;
for (int k = 1; k <= 15; k++)
f[j][k] = f[f[j][k - 1]][k - 1];
}
}
}
}
//标准lca
int lca(int a, int b) {
if (depth[a] < depth[b]) swap(a, b);
for (int i = 15; i >= 0; i--)
if (depth[f[a][i]] >= depth[b]) a = f[a][i];
if (a == b) return a;
for (int i = 15; i >= 0; i--)
if (f[a][i] != f[b][i])
a = f[a][i], b = f[b][i];
return f[a][0];
}

int dfs(int u) {
int res = d[u]; //自己的点权
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (depth[j] > depth[u]) {
int s = dfs(j);
if (s == 0)
ans += m; //删除任意一条附加条均可
else if (s == 1)
ans += 1; //删除组成环的那条附加边
res += s; //加上自己孩子的点权
}
}
return res; //以u为根的所有点的点权
}
int main() {
int a, b;
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 1; i < n; i++) {
cin >> a >> b;
add(a, b), add(b, a);
}
// lca的准备动作
bfs(1);
//读入附加边
for (int i = 0; i < m; i++) {
cin >> a >> b;
//两个端点的点权+1
d[a]++, d[b]++;
//求两个端点a,b的lca
int p = lca(a, b);
// lca的点权-2
d[p] -= 2;
}
//深度遍历一次,在树上找出所有点权为0或为1的点的个数,就是答案
dfs(1);
printf("%d\n", ans);
return 0;
}