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\)

  1. \(len > a\):显然,\(v\) 到其子树外的最长链就是 \(x\) 到其子树外的最长链加上边 \((x, v)\),而 LCA 不变。

  2. \(len = a\):此时最长链有两条,它们交于 \(x\)。所以此时 LCA 变为 \(x\),而最长链与情况 \(1\) 相同。

  3. \(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;
}
posted @ 2024-01-31 22:10  forgotmyhandle  阅读(14)  评论(0)    收藏  举报