bzoj 3572[HNOI2014]世界树

欢迎点此阅读QvQ

Descripiton

给定一棵树,有若干个询问,每次给定 m 个点,每个点都被这m个点中最近(距离相同,编号小的近)的点管辖。问 m 个点分别管几个点
m300000

Solution

一个很经典的题,通过这题学到了一个叫虚树的东西,修为得到的精进
虚树就是包含了给定点,并收缩了不分叉边的连通子图
然后我们讨论下虚树的构建
先给代码
t 表示虚树的节点,h是给定点, st 是栈

void build(int m) {
    int top = 0;
    sort(h + 1, h + m + 1, cmp);
    for (int i = 1; i <= m; ++i) {
        if (!top)   fa[st[++top] = h[i]] = 0;
        else {
            int x = lca(st[top], h[i]);
            for (; dep[st[top]] > dep[x]; --top)
                if (dep[st[top - 1]] <= dep[x]) fa[st[top]] = x;
            if (st[top] != x) {
                fa[x] = st[top];
                t[++tot] = st[++top] = x;
                near[x] = mp(0x3f3f3f3f, 0);
            }
            fa[st[++top] = h[i]] = x;
        }
    }
}

我们将给定点按lca递增排序,用一个栈表示已构建的虚树上以最后一个点为断点的链,设栈顶元素为p,当前点为x,然后我们求出lca
有两种情况
* (1): p x lca 的两棵子树下
* (2): lca p
对于第二种情况,由于dfs序递增, lca 不可能是 x ,因为lca一定不晚于 p 访问,这种情况直接连边即可
对于第一种情况,由于dfn[lca]<dfn[p]<dfn[x],说明 p 的子树我们一定都处理完了,否则一定会在x前处理
由于p子树已经处理完,我们可以退栈辣,退栈直到lca夹在两个栈元素之间,处理完边的关系,再退一次,这条链就处理完辣!
然后就可以继续把当前x加入栈中,继续处理这条链,代码很短,但是需要理解一会~

容易发现构建的虚树最多 2m 个点,完全可以接受
构建完虚树就是树形dp的事情了,先按 dfs 序正反两遍扫出虚树上每个点被哪个点管理,并记录距离。(容易想到用个pair来存,编程复杂度会少一些)
继续按dfs序递增处理
如果是虚树的根,那么首先对管理它的点的贡献就是 nsz[rt]
假设当前点是 x ,虚树上的父节点是fa, rt 同时是真树上 x 的祖先和fa的儿子,考虑fa x <script type="math/tex" id="MathJax-Element-29">x</script>是否被同一个点管辖,如果是,中间都属于同一个点
否则考虑尽量靠中间的点,分奇偶讨论下即可

Code

#include <bits/stdc++.h>//虚树
using namespace std;
#define pb push_back
#define mp make_pair
#define F first
#define S second
typedef long long LL;
typedef pair<int, int> pii;
const int N = 3e5 + 5;
vector<int> g[N];
pii near[N];
int n, tot, ind, dep[N], dfn[N], sz[N], f[N][20], pos[N], h[N], t[N], st[N], fa[N], w[N], dis[N], ans[N];
bool cmp(const int &x, const int &y) {
    return dfn[x] < dfn[y];
}
int find(int x, int d) {
    for (int i = 19; i >= 0; --i)
        if (dep[f[x][i]] >= d)  x = f[x][i];
    return x;
}
int lca(int x, int y) {
    if (dep[x] < dep[y])    swap(x, y);
    for (int i = 19; i >= 0; --i)
        if (dep[f[x][i]] >= dep[y]) x = f[x][i];
    if (x == y) return x;
    for (int i = 19; i >= 0; --i)
        if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
    return f[x][0];
}
void dfs(int u) {
    sz[u] = 1;
    dfn[u] = ++ind;
    for (int i = 0, v; i < g[u].size(); ++i) {
        v = g[u][i];
        if (v == f[u][0])   continue;
        f[v][0] = u;
        for (int j = 1; j <= 19; ++j)   f[v][j] = f[f[v][j - 1]][j - 1];
        dep[v] = dep[u] + 1;
        dfs(v);
        sz[u] += sz[v];
    }
}
void build(int m) {
    int top = 0;
    sort(h + 1, h + m + 1, cmp);
    for (int i = 1; i <= m; ++i) {
        if (!top)   fa[st[++top] = h[i]] = 0;
        else {
            int x = lca(st[top], h[i]);
            for (; dep[st[top]] > dep[x]; --top)
                if (dep[st[top - 1]] <= dep[x]) fa[st[top]] = x;
            if (st[top] != x) {
                fa[x] = st[top];
                t[++tot] = st[++top] = x;
                near[x] = mp(0x3f3f3f3f, 0);
            }
            fa[st[++top] = h[i]] = x;
        }
    }
}
void work() {
    tot = 0;
    int m;
    scanf("%d", &m);
    for (int i = 1; i <= m; ++i) {
        scanf("%d", &h[i]);
        t[++tot] = pos[i] = h[i], ans[h[i]] = 0, near[h[i]] = mp(0, h[i]);
    }
    build(m);
    sort(t + 1, t + tot + 1, cmp);
    for (int i = 1; i <= tot; ++i) {
        int x = t[i];
        w[x] = sz[x];
        if (i > 1)  dis[x] = dep[x] - dep[fa[x]];
    }
    for (int i = tot; i > 1; --i) {
        int x = t[i];
        near[fa[x]] = min(near[fa[x]], mp(near[x].F + dis[x], near[x].S));
    }
    for (int i = 2; i <= tot; ++i) {
        int x = t[i];
        near[x] = min(near[x], mp(near[fa[x]].F + dis[x], near[fa[x]].S));
    }
    for (int i = 1; i <= tot; ++i) {
        int x = t[i];
        if (i == 1) ans[near[x].S] += n - sz[x];
        else {
            int rt = find(x, dep[fa[x]] + 1);
            int sum = sz[rt] - sz[x];
            w[fa[x]] -= sz[rt];
            if (near[fa[x]].S == near[x].S) ans[near[x].S] += sum;
            else {
                int mid = dep[x] - (near[fa[x]].F - near[x].F + dis[x]) / 2;
                if ((near[fa[x]].F + near[x].F + dis[x]) % 2 == 0 && near[fa[x]].S < near[x].S) ++mid;
                int tmp = sz[find(x, mid)] - sz[x];
                ans[near[fa[x]].S] += sum - tmp;
                ans[near[x].S] += tmp;
            }
        }
    }
    for (int i = 1; i <= tot; ++i)  ans[near[t[i]].S] += w[t[i]];
    for (int i = 1; i <= m; ++i)    printf("%d ", ans[pos[i]]);
    puts("");
}
void gao() {
    scanf("%d", &n);
    for (int i = 1, u, v; i < n; ++i) {
        scanf("%d%d", &u, &v);
        g[u].pb(v), g[v].pb(u);
    }
    dfs(dep[1] = 1);
    int q;
    scanf("%d", &q);
    while (q--) work();
}
int main() {
    gao();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值