[NOIp2016]天天爱跑步 线段树合并

[NOIp2016]天天爱跑步

LG传送门

作为一道被毒瘤出题人们玩坏了的NOIp经典题,我们先不看毒瘤的“动态爱跑步”和“天天爱仙人掌”,回归一下本来的味道。

对于一个人,他的路程会分为两段,一段向上(根),一段向下,考虑在向上过程中他能产生贡献的观察者具有什么性质:设出发点深度为\(dep[x]\),观察者深度为\(dep[y]\),观察的时间为\(t\),需满足\(dep[x] - dep[y] = t\),换句话说就是\(dep[y] + t = dep[x]\)。向下那一段的推导类似,下面的部分也只以向上的路径为例来解释。

现在我们记录每个点下方出发点深度为\(dep[x]\)的人数,需要对于每个出发点更新出发点到出发点与目的地的lca的所有点,想到到开一颗权值线段树,用线段树合并不断把信息往上传,在lca处消除这次更新的影响,这样一来直接我们就可以在树上统计答案了。向下路径的处理与向上路径类似,这里就不再赘述,上代码。

#include <cstdio>
#include <cctype>
#include <vector>
#define R register
#define I inline
#define B 1000000
using namespace std;
const int N = 300001, M = 15000007;
char buf[B], *p1, *p2;
I char gc() { return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, B, stdin), p1 == p2) ? EOF : *p1++; }
I int rd() {
    R int f = 0;
    R char c = gc();
    while (c < 48 || c > 57)
        c = gc();
    while (c > 47 && c < 58)
        f = f * 10 + (c ^ 48), c = gc();
    return f;
}
int w[N], s[N], t[N], p[N], q[N], d[N], r[N], u[N], v[N], o[N], n, C, S;
struct T{int f, p, q;}e[M];
vector <int> g[N];
I void swp(int &x, int &y) { x ^= y, y ^= x, x ^= y; }
void dfs1(int x, int f) {
    p[x] = f, d[x] = d[f] + 1, t[x] = 1;
    for (R int i = 0, y, m = 0; i < s[x]; ++i)
        if ((y = g[x][i]) ^ f) {
            dfs1(y, x), t[x] += t[y];
            if (t[y] > m)
                m = t[y], q[x] = y;
        }
}
void dfs2(int x, int t) {
    r[x] = t;
    if (q[x])
        dfs2(q[x], t);
    for (R int i = 0, y; i < s[x]; ++i)
        if ((y = g[x][i]) ^ p[x] && y ^ q[x])
            dfs2(y, y);
}
I int lca(int x, int y) {
    while (r[x] ^ r[y]) {
        if (d[r[x]] < d[r[y]])
            swp(x, y);
        x = p[r[x]];
    }
    if (d[x] > d[y])
        swp(x, y);
    return x;
}
void ins(int &k, int l, int r, int x, int v) {
    if (!k)
        k = ++C;
    e[k].f += v;
    if (l == r)
        return ;
    R int m = l + r >> 1;
    if (x <= m)
        ins(e[k].p, l, m, x, v);
    else
        ins(e[k].q, m + 1, r, x, v);
}
int mrg(int k, int t) {
    if (!k)
        return t;
    if (!t)
        return k;
    e[k].f += e[t].f, e[k].p = mrg(e[k].p, e[t].p), e[k].q = mrg(e[k].q, e[t].q);
    return k;
}
int qry(int k, int l, int r, int x) {
    if (l == r)
        return e[k].f;
    R int m = l + r >> 1;
    if (x <= m)
        return qry(e[k].p, l, m, x);
    else
        return qry(e[k].q, m + 1, r, x);
}
void dfs(int x) {
    for (R int i = 0, y; i < s[x]; ++i)
        if ((y = g[x][i]) ^ p[x])
            dfs(y), u[x] = mrg(u[x], u[y]), v[x] = mrg(v[x], v[y]);
    o[x] = qry(u[x], 1, S, w[x] + d[x]) + qry(v[x], 1, S, w[x] - d[x] + n);
}
int main() {
    R int m, i, x, y, a;
    n = rd(), m = rd(), S = n << 1;
    for (i = 1; i < n; ++i)
        x = rd(), y = rd(), g[x].push_back(y), g[y].push_back(x);
    for (i = 1; i <= n; ++i)
        s[i] = g[i].size(), w[i] = rd();
    dfs1(1, 0), dfs2(1, 1);
    for (i = 1; i <= m; ++i)
        x = rd(), y = rd(), a = lca(x, y), ins(u[x], 1, S, d[x], 1), ins(u[a], 1, S, d[x], -1), ins(v[y], 1, S, d[x] - (d[a] << 1) + n, 1), ins(v[p[a]], 1, S, d[x] - (d[a] << 1) + n, -1);
    dfs(1);
    for (i = 1; i <= n; ++i)
        printf("%d ", o[i]);
    return 0;
}

因为减的结果可能有负数,所以加上一个\(n\),值域变两倍。

猜你喜欢

转载自www.cnblogs.com/cj-chd/p/10353138.html