CF384E Pilgrims
分析
约定:钦定 \(1\) 为根。下称修道院所在点为黑点,城镇所在点为白点。记 \(F(x)\) 为 \(x\) 的父亲。
对于每个黑点 \(x\),我们可以求出将哪些白点删去会使得其要求无法达成。显然,删去以 \(x\) 为根时 到 \(x\) 所有距离最远的点的 LCA 与 \(x\) 的路径上的点一定会使 \(x\) 的要求无法达成。于是,我们可以求出以 \(x\) 为起点的最长链,以及以 \(x\) 为根时所有到 \(x\) 距离最远点的 LCA。
以下暂时不考虑点的黑白。
以一个点为起点的链可以分为两种:终点在其子树内的,和终点在其子树外的。我们将两种情况分开计算。即,算出以该点为起点,终点在其子树内的最长链及 LCA 和终点在其子树外的最长链和 LCA。子树内的问题是平凡的,只需要记录最长链和次长链及其出处即可。问题主要在于终点在其子树外的最长链。
显然,对于每个点 \(x\),其子树外的点有两种。一种在 \(F(x)\) 的子树内,一种在 \(F(x)\) 的子树外。于是最长链也分为两种:终点在 \(F(x)\) 子树外的,和终点在 \(F(x)\) 子树内而在 \(x\) 子树外的。于是我们可以 dfs。过程中维护当前点 \(x\) 到其子树外的点的最长链与其(以 \(x\) 为根时的) LCA。接下来我们要将这两个信息转移到其某个儿子 \(v\)。
设 \(x\) 子树内不经过 \(v\) 的最长链的长度为 \(a\),\(x\) 到其子树外点的最长链长度为 \(len\)。
-
\(len > a\):显然,\(v\) 到其子树外的最长链就是 \(x\) 到其子树外的最长链加上边 \((x, v)\),而 LCA 不变。
-
\(len = a\):此时最长链有两条,它们交于 \(x\)。所以此时 LCA 变为 \(x\),而最长链与情况 \(1\) 相同。
-
\(len < a\):此时我们并不知道最长链有几条,因为 \(x\) 到其子树内不经过 \(v\) 的最长链可以有多条。于是再记录 \(x\) 到其子树内的第三长链,这样就一定可以知道 \(x\) 到其子树内不经过 \(v\) 的次长链。若次长链与最长链相等,则 LCA 也为 \(x\)。否则设最长链位于 \(x\) 的儿子 \(y\) 的子树中,则 LCA 为 \(y\) 到其子树内所有最远点的 LCA。
这样我们就求出了每个点 \(x\) 子树内外的最长链及其 LCA。接下来比较两边的最长链。若两边相等,则 \(x\) 的要求不可能不满足。否则将 \(x\) 到较长链所在侧的 LCA 的路径都加上 \(1\),以说明这些点可以使 \(x\) 的要求不被满足。这可以树上差分。于是就 ok 了。
总的来说,我们要求出 \(f[i][0 / 1 / 2]\) 表示 \(i\) 到其子树内最长、次长、第三长链的长度,\(g[i][0 / 1 / 2]\) 表示 \(f[i][0 / 1 / 2]\) 来自 \(i\) 的哪一棵子树。\(p[x]\) 表示 \(x\) 到其子树内所有最远点的 LCA。这些都可以一遍 dfs 完成。
考虑黑点也很简单,只需要将 \(f\) 和 \(g\) 的定义变成到子树内黑点的三条链的长度和出处,\(p\) 变成子树内最远黑点的 LCA。第二次 dfs 中 \(len\) 变成到子树外最远黑点的距离,LCA 也类似。于是就真 ok 了。
代码
由于写了树剖求 LCA,所以看起来可能非常长。
#include <iostream>
using namespace std;
int n, m;
int head[100005], nxt[200005], to[200005], ew[200005], ecnt;
void add(int u, int v, int ww) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = ww; }
int f[100005][3], g[100005][3], p[100005];
int F[100005];
int dfn[100005];
int _dfn[100005], ncnt;
// 这个 namespace 可以略过不看
namespace LCA {
int top[100005], son[100005], dep[100005], sz[100005], f[100005];
void dfs1(int x, int fa, int d) {
f[x] = fa;
sz[x] = 1;
dep[x] = d;
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (v != fa) {
dfs1(v, x, d + 1);
sz[x] += sz[v];
if (sz[v] > sz[son[x]])
son[x] = v;
}
}
}
void dfs2(int x, int t) {
top[x] = t;
if (!son[x])
return;
dfs2(son[x], t);
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (v != son[x] && v != f[x])
dfs2(v, v);
}
}
void ini() { dfs1(1, 0, 1), dfs2(1, 1); }
int LCA(int x, int y) {
while (top[x] ^ top[y]) (dep[top[x]] < dep[top[y]]) ? (y = f[top[y]]) : (x = f[top[x]]);
return (dep[x] < dep[y] ? x : y);
}
}
// 以上 namespace 可以略过不看
bool b[100005];
int S[100005];
int sb[100005];
void dfs1(int x, int fa) {
_dfn[dfn[x] = ++ncnt] = x;
F[x] = fa;
sb[x] = b[x];
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (v != fa) {
dfs1(v, x);
sb[x] += sb[v];
if (sb[v]) {
int k = f[v][0] + ew[i];
if (k > f[x][0])
f[x][2] = f[x][1], f[x][1] = f[x][0], f[x][0] = k, g[x][2] = g[x][1], g[x][1] = g[x][0], g[x][0] = v;
else if (k > f[x][1])
f[x][2] = f[x][1], f[x][1] = k, g[x][2] = g[x][1], g[x][1] = v;
else if (k > f[x][2])
f[x][2] = k, g[x][2] = v;
}
}
}
p[x] = ((f[x][0] == f[x][1]) ? x : p[g[x][0]]);
}
void dfs2(int x, int fa, int len, int lca) {
if (m - sb[x] == 0) // 如果子树外没有黑点!直接清掉,因为下面传递信息时没考虑黑点
len = lca = 0;
if (b[x]) {
if (len > f[x][0]) {
int tmp = LCA::LCA(x, lca);
S[x]++, S[lca]++, S[tmp]--, S[F[tmp]]--;
} else if (len < f[x][0])
S[F[x]]--, S[p[x]]++;
// 两边相等时这个黑点一定不可能被隔断,所以不用加
}
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (v != fa) { // 这里先不考虑黑点
int a, b, c;
v == g[x][0] ? (a = f[x][1], b = f[x][2]) : (v == g[x][1] ? (a = f[x][0], b = f[x][2]) : (a = f[x][0], b = f[x][1]));
c = g[x][v == g[x][0]];
if (len > a)
dfs2(v, x, len + ew[i], lca);
else if (len == a)
dfs2(v, x, len + ew[i], x);
else if (len < a)
dfs2(v, x, a + ew[i], a == b ? x : p[c]);
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1, x; i <= m; i++) cin >> x, b[x] = 1;
for (int i = 1, u, v, ww; i < n; i++) {
cin >> u >> v >> ww;
add(u, v, ww);
add(v, u, ww);
}
LCA::ini();
dfs1(1, 0);
dfs2(1, 0, 0, 0);
int mxc = 0, cnt = 0; // 答案和答案个数
for (int i = n; i; i--) { // dfs 序枚举代替 dfs 做子树和
int x = _dfn[i];
S[F[x]] += S[x];
if (!b[x]) {
S[x] > mxc ? (mxc = S[x], cnt = 0) : 0;
cnt += (mxc == S[x]);
}
}
cout << mxc << " " << cnt << "\n";
return 0;
}

浙公网安备 33010602011771号