题目链接:​​https://loj.ac/p/10131​

题目大意:

Dark 是一张无向图,图中有 \(N\) 个节点和两类边,一类边被称为主要边,而另一类被称为附加边。Dark 有 \(N-1\) 条主要边,并且 Dark 的任意两个节点之间都存在一条只由主要边构成的路径。另外,Dark 还有 \(M\) 条附加边。

你的任务是把 Dark 斩为不连通的两部分。一开始 Dark 的附加边都处于无敌状态,你只能选择一条主要边切断。一旦你切断了一条主要边,Dark 就会进入防御模式,主要边会变为无敌的而附加边可以被切断。但是你的能力只能再切断 Dark 的一条附加边。

现在你想要知道,一共有多少种方案可以击败 Dark。注意,就算你第一步切断主要边之后就已经把 Dark 斩为两截,你也需要切断一条附加边才算击败了 Dark。

解题思路:

根据题意,“主要边”构成一棵树,“附加边”则是“非树边”。把一条附加边 \((x,y)\) 添加到主要边构成的树中,会与树上 \(x,y\) 之间的路径一起形成一个环。如果第一步选择切断 \(x,y\) 之间路径上的某条边,那么第二步就必须切断附加边 \((x,y)\) ,才能使 Dark 被斩为不连通的两部分。

因此,我们称每条附加边 \((x,y)\) 都把树上 \(x,y\) 之间的路径上的每条边“覆盖了一次”。我们只需统计出每条“主要边”被覆盖了多少次。若第一步把覆盖 \(0\) 次的主要边切断,则第二步可以切断任意一条附加边。若第一步把覆盖 \(1\) 次的主要边切断,则第 \(2\) 步方法唯一。若第一步把覆盖 \(2\) 次及以上的主要边切断,则第二步无论如何操作都不能击败 Dark。这样我们就得到了击败 Dark 的方案数。

使用树上差分(边差分),即对于任意 \((x,y)\),设它们的 LCA 为 \(p\),差分数组为 \(d\),则 \(d[u]\) 和 \(d[v]\) 依次加 \(1\),而 \(d[p]\) 减 \(2\)。

示例程序:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
int n, m, pa[maxn][21], dep[maxn];
vector<int> g[maxn];
int d[maxn], ans;
void dfs(int u, int p) {
dep[u] = dep[p] + 1;
pa[u][0] = p;
for (int i = 1; (1<<i) <= dep[u]; i ++)
pa[u][i] = pa[ pa[u][i-1] ][i-1];
for (auto v : g[u])
if (v != p)
dfs(v, u);
}
int lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = 20; i >= 0; i --) {
if (dep[ pa[x][i] ] >= dep[y]) x = pa[x][i];
if (x == y) return x;
}
for (int i = 20; i >= 0; i --) {
if (pa[x][i] != pa[y][i]) {
x = pa[x][i];
y = pa[y][i];
}
}
return pa[x][0];
}
void dfs2(int u, int p) {
for (auto v : g[u])
if (v != p)
dfs2(v, u), d[u] += d[v];
}
int main() {
ios::sync_with_stdio(0);
cin >> n >> m;
for (int i = 1; i < n; i ++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
for (int i = 0; i < m; i ++) {
int u, v, p;
cin >> u >> v;
p = lca(u, v);
d[u] ++;
d[v] ++;
d[p] -= 2;
}
dfs2(1, 0);
for (int i = 2; i <= n; i ++) {
if (d[i] == 0) ans += m;
else if (d[i] == 1)ans ++;
}
cout << ans << endl;
return 0;
}