T3 题解

还有人在改 10.8 的 tg 吗

$\sum\limits_{d(x,y)\ge k}d(x,y)=\sum\limits_{d(1,y)\ge d(1,x)+k}d(1,y)-d(1,x)\sum\limits_{d(1,y)\ge d(1,x)+k}1$。

把树的 DFS 序搞下来,维护两个东西:

  1. 区间大于等于某个数的数的和。
  2. 区间大于等于某个数的数的个数。

两部分都是平凡的。

要是别的题,题解应该结束了。但这是考试题,所以要讲一下怎么维护。

树状数组

考虑将询问按 $d(1,x)+k$ 排序,按深度依次往树状数组里加点,使得处理询问 $(x,k)$ 时所有 $d(1,y)\ge d(1,x)+k$ 的点 $y$ 在树状数组中。

#include <cstdio>
#include <algorithm>
using namespace std;
struct P
{
    int x;
    long long d;
} X[200001];
struct Q
{
    int x, z, i;
} Y[200001];
struct E
{
    int v, w, t;
} e[200001];
int n, m, x, z, c, p, d[200001], s[200001], h[200001];
long long a[200001], q[200001];
void A(int u, int v, int w)
{
    e[++c] = (E){v, w, h[u]};
    h[u] = c;
}
void D(int u)
{
    X[p] = {p + 1, a[u]};
    d[u] = ++p;
    s[u] = 1;
    for (int i = h[u], v; i; i = e[i].t)
        D(v = e[i].v), s[u] += s[v];
}
struct V
{
    long long s;
    int c;
    void operator+=(V x)
    {
        s += x.s;
        c += x.c;
    }
    void operator-=(V x)
    {
        s -= x.s;
        c -= x.c;
    }
} C[200001];
void M(int x, V k)
{
    for (; x <= n; x += x & -x)
        C[x] += k;
}
V S(int x, int y)
{
    V q = {0, 0};
    --x;
    for (; y > x; y &= y - 1)
        q += C[y];
    for (; x > y; x &= x - 1)
        q -= C[x];
    return q;
}
int main()
{
    scanf("%d", &n);
    for (int i = 2, p, w; i <= n; ++i)
        scanf("%d%d", &p, &w), A(p, i, w), a[i] = a[p] + w;
    D(1);
    scanf("%d", &m);
    for (int i = 0; i < m; ++i)
        scanf("%d%d", &Y[i].x, &Y[i].z), Y[i].i = i;
    sort(X, X + n, [](P u, P v)
         { return u.d > v.d; });
    sort(Y, Y + m, [](Q u, Q v)
         { return a[u.x] + u.z > a[v.x] + v.z; });
    for (int i = 0, j = 0; i < m; ++i)
    {
        x = Y[i].x;
        z = Y[i].z;
        for (; j < n && X[j].d >= a[x] + z; ++j)
            M(X[j].x, {X[j].d, 1});
        V G = S(d[x], d[x] + s[x] - 1);
        q[Y[i].i] = G.s - a[x] * G.c;
    }
    for (int i = 0; i < m; ++i)
        printf("%lld\n", q[i]);
    return 0;
}

跑了 998ms。

主席树

值域相关静态区间信息,主席树!

比较板子,不细讲了。很容易写出 naive 的动态开点主席树:

#include <cstdio>
#define G int m = s + t >> 1
struct V
{
    long long s;
    int c;
    void operator+=(V x)
    {
        s += x.s;
        c += x.c;
    }
};
struct E
{
    int v, w, t;
} e[200001];
int n, m, x, z, c, p, g, d[200001], k[200001], s[200001], h[200001];
long long a[200001];
void A(int u, int v, int w)
{
    e[++c] = (E){v, w, h[u]};
    h[u] = c;
}
void D(int u)
{
    d[u] = ++p;
    k[p] = u;
    s[u] = 1;
    for (int i = h[u], v; i; i = e[i].t)
        D(v = e[i].v), s[u] += s[v];
}
struct T
{
    int l, r;
    V v;
    T() : l(0), r(0), v{0, 0} {}
    void u();
} P[20000050];
int r[200001];
void T::u()
{
    v.s = v.c = 0;
    if (l)
        v.s += P[l].v.s, v.c += P[l].v.c;
    if (r)
        v.s += P[r].v.s, v.c += P[r].v.c;
}
void M(long long l, V k, long long s, long long t, int &p)
{
    ++g;
    if (p)
        P[g] = P[p];
    p = g;
    if (s == t)
    {
        P[p].v += k;
        return;
    }
    G;
    if (l <= m)
        M(l, k, s, m, P[p].l);
    else
        M(l, k, m + 1, t, P[p].r);
    P[p].u();
}
V Q(long long l, long long r, long long s, long long t, int p)
{
    if (!p)
        return {0, 0};
    if (l <= s && t <= r)
        return {P[p].v.s, P[p].v.c};
    G;
    V q = {0, 0};
    if (l <= m)
        q += Q(l, r, s, m, P[p].l);
    if (r > m)
        q += Q(l, r, m + 1, t, P[p].r);
    return q;
}
int main()
{
    scanf("%d", &n);
    for (int i = 2, p, w; i <= n; ++i)
        scanf("%d%d", &p, &w), A(p, i, w), a[i] = a[p] + w;
    D(1);
    r[0] = g = 1;
    for (int i = 1; i <= n; ++i)
        r[i] = r[i - 1], M(a[k[i]], {a[k[i]], 1}, 0, 6e9, r[i]);
    scanf("%d", &m);
    while (m--)
    {
        scanf("%d%d", &x, &z);
        V L = Q(a[x] + z, 6e9, 0, 6e9, r[d[x] - 1]),
          R = Q(a[x] + z, 6e9, 0, 6e9, r[d[x] + s[x] - 1]);
        printf("%lld\n", R.s - L.s - a[x] * (R.c - L.c));
    }
    return 0;
}

然后就 MLE 了。考虑离散化。

#include <algorithm>
#include <cstdio>
#define G int m = s + t >> 1
#define H(x) lower_bound(v, v + l, x) - v + 1
using namespace std;
struct Q
{
    int x, z;
} q[200001];
int l;
long long v[400001];
struct V
{
    long long s;
    int c;
    void operator+=(V x)
    {
        s += x.s;
        c += x.c;
    }
};
struct E
{
    int v, w, t;
} e[200001];
int n, m, x, z, c, p, g, d[200001], k[200001], s[200001], h[200001];
long long a[200001];
void A(int u, int v, int w)
{
    e[++c] = (E){v, w, h[u]};
    h[u] = c;
}
void D(int u)
{
    d[u] = ++p;
    k[p] = u;
    s[u] = 1;
    for (int i = h[u], v; i; i = e[i].t)
        D(v = e[i].v), s[u] += s[v];
}
struct T
{
    int l, r;
    V v;
    T() : l(0), r(0), v{0, 0} {}
    void u();
} P[4000001];
int r[200001];
void T::u()
{
    v.s = v.c = 0;
    if (l)
        v.s += P[l].v.s, v.c += P[l].v.c;
    if (r)
        v.s += P[r].v.s, v.c += P[r].v.c;
}
void M(long long l, V k, long long s, long long t, int &p)
{
    ++g;
    if (p)
        P[g] = P[p];
    p = g;
    if (s == t)
    {
        P[p].v += k;
        return;
    }
    G;
    if (l <= m)
        M(l, k, s, m, P[p].l);
    else
        M(l, k, m + 1, t, P[p].r);
    P[p].u();
}
V Q(long long l, long long r, long long s, long long t, int p)
{
    if (!p)
        return {0, 0};
    if (l <= s && t <= r)
        return {P[p].v.s, P[p].v.c};
    G;
    V q = {0, 0};
    if (l <= m)
        q += Q(l, r, s, m, P[p].l);
    if (r > m)
        q += Q(l, r, m + 1, t, P[p].r);
    return q;
}
int main()
{
    scanf("%d", &n);
    for (int i = 2, p, w; i <= n; ++i)
        scanf("%d%d", &p, &w), A(p, i, w), v[l++] = a[i] = a[p] + w;
    D(1);
    scanf("%d", &m);
    for (int i = 0; i < m; ++i)
        scanf("%d%d", &x, &z), v[l++] = a[x] + z, q[i] = {x, z};
    sort(v, v + l);
    l = unique(v, v + l) - v;
    r[0] = g = 1;
    for (int i = 1; i <= n; ++i)
        r[i] = r[i - 1], M(H(a[k[i]]), {a[k[i]], 1}, 0, l, r[i]);
    for (int i = 0; i < m; ++i)
    {
        x = q[i].x;
        z = q[i].z;
        V L = Q(H(a[x] + z), l, 0, l, r[d[x] - 1]),
          R = Q(H(a[x] + z), l, 0, l, r[d[x] + s[x] - 1]);
        printf("%lld\n", R.s - L.s - a[x] * (R.c - L.c));
    }
    return 0;
}

跑了 4.13s。

分块

块内排序,散块暴力,整块 lower_bound,预处理一下块内后缀和。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
struct E
{
    int v, w, t;
} e[200001];
int n, m, x, z, c, p, b, l, r, k, L[450], R[450], d[200001], s[200001], t[200001], h[200001];
long long a[200001], f[200001], g[200001];
void A(int u, int v, int w)
{
    e[++c] = (E){v, w, h[u]};
    h[u] = c;
}
void D(int u)
{
    d[u] = ++p;
    s[u] = 1;
    for (int i = h[u], v; i; i = e[i].t)
        D(v = e[i].v), s[u] += s[v];
}
struct V
{
    long long s;
    int c;
    V operator+(V x) { return {s + x.s, c + x.c}; }
    void operator+=(V x) { *this = *this + x; }
} S[200001];
int main()
{
    scanf("%d", &n);
    b = sqrt(n);
    t[1] = 1;
    for (int i = 2, p, w; i <= n; ++i)
        scanf("%d%d", &p, &w), A(p, i, w), a[i] = a[p] + w, t[i] = (i - 1) / b + 1;
    for (int i = 1; i <= t[n]; ++i)
        L[i] = (i - 1) * b + 1, R[i] = min(L[i] + b - 1, n);
    D(1);
    for (int i = 1; i <= n; ++i)
        f[d[i]] = a[i];
    for (int i = 1; i <= t[n]; ++i)
    {
        memcpy(g + L[i], f + L[i], R[i] - L[i] + 1 << 3);
        sort(g + L[i], g + R[i] + 1);
        for (int j = R[i]; j >= L[i]; --j)
            S[j] = S[j + 1] + (V){g[j], 1};
    }
    scanf("%d", &m);
    while (m--)
    {
        scanf("%d%d", &x, &z);
        l = d[x];
        r = d[x] + s[x] - 1;
        V G = {0, 0};
        if (t[l] == t[r])
        {
            for (int i = l; i <= r; ++i)
                if (f[i] >= a[x] + z)
                    G += {f[i], 1};
        }
        else
        {
            for (int i = l; i <= R[t[l]]; ++i)
                if (f[i] >= a[x] + z)
                    G += {f[i], 1};
            for (int i = L[t[r]]; i <= r; ++i)
                if (f[i] >= a[x] + z)
                    G += {f[i], 1};
            for (int i = t[l] + 1; i < t[r]; ++i)
                if ((k = lower_bound(g + L[i], g + R[i] + 1, a[x] + z) - g) <= R[i])
                    G += S[k];
        }
        printf("%lld\n", G.s - a[x] * G.c);
    }
    return 0;
}

跑了 2.5s。

莫队+值域分块

莫队需要桶上 $O(n\sqrt n)$ 次单点修改,$O(m)$ 次区间查询。用值域分块平衡复杂度。

#include <algorithm>
#include <cmath>
#include <cstdio>
#define H(x) lower_bound(v, v + l, x) - v + 1
using namespace std;
struct Q
{
    int l, r, p, i;
    long long k;
} Y[200001];
int l;
long long v[400001];
struct E
{
    int v, w, t;
} e[200001];
int n, m, x, z, c, p, b, L[650], R[650], X[200001], d[200001], k[200001], s[200001], h[200001], t[400001];
long long a[200001], q[200001];
void A(int u, int v, int w)
{
    e[++c] = (E){v, w, h[u]};
    h[u] = c;
}
void D(int u)
{
    d[u] = ++p;
    k[p] = u;
    s[u] = 1;
    for (int i = h[u], v; i; i = e[i].t)
        D(v = e[i].v), s[u] += s[v];
}
struct V
{
    long long s;
    int c;
    void operator+=(V x)
    {
        s += x.s;
        c += x.c;
    }
    void operator-=(V x)
    {
        s -= x.s;
        c -= x.c;
    }
} f[650], g[400001];
void I(int x)
{
    g[X[x]] += {a[k[x]], 1};
    f[t[X[x]]] += {a[k[x]], 1};
}
void O(int x)
{
    g[X[x]] -= {a[k[x]], 1};
    f[t[X[x]]] -= {a[k[x]], 1};
}
V S(int x)
{
    V q = {0, 0};
    if (t[x] == t[l])
    {
        for (int i = x; i <= l; ++i)
            q += g[i];
        return q;
    }
    for (int i = x; i <= R[t[x]]; ++i)
        q += g[i];
    for (int i = t[x] + 1; i < t[l]; ++i)
        q += f[i];
    for (int i = L[t[l]]; i <= l; ++i)
        q += g[i];
    return q;
}
int main()
{
    scanf("%d", &n);
    b = sqrt(n);
    for (int i = 2, p, w; i <= n; ++i)
        scanf("%d%d", &p, &w), A(p, i, w), v[l++] = a[i] = a[p] + w;
    D(1);
    scanf("%d", &m);
    for (int i = 0; i < m; ++i)
        scanf("%d%d", &x, &z), Y[i] = {d[x], d[x] + s[x] - 1, (d[x] - 1) / b + 1, i, v[l++] = a[x] + z};
    sort(Y, Y + m, [](Q u, Q v)
         { return u.p == v.p ? u.p & 1 ? u.r < v.r : u.r > v.r : u.p < v.p; });
    sort(v, v + l);
    b = sqrt(l = unique(v, v + l) - v);
    for (int i = 1; i <= l; ++i)
        t[i] = (i - 1) / b + 1;
    for (int i = 1; i <= t[l]; ++i)
        L[i] = (i - 1) * b + 1, R[i] = min(L[i] + b - 1, l);
    for (int i = 1; i <= n; ++i)
        X[i] = H(a[k[i]]);
    for (int i = 0, x = 1, y = 0; i < m; ++i)
    {
        while (x < Y[i].l)
            O(x++);
        while (x > Y[i].l)
            I(--x);
        while (y > Y[i].r)
            O(y--);
        while (y < Y[i].r)
            I(++y);
        V G = S(H(Y[i].k));
        q[Y[i].i] = G.s - a[k[Y[i].l]] * G.c;
    }
    for (int i = 0; i < m; ++i)
        printf("%lld\n", q[i]);
    return 0;
}

跑了 3.26s。

吉司机线段树

Link

跑了 2.20s。

Update:可以卡成 $O(n^2)$,寄了。

后记

做法 用时 在线/离线
树状数组 998ms 离线
主席树 4.13s 离线
分块 2.5s 在线
莫队+值域分块 3.26s 离线
吉司机线段树 2.20s 在线

统一 scanf 输入。

代码格式化过。

主席树板题卡主席树。

为什么卡空间啊。

为什么根号比 $\log$ 快啊。

为什么根号 $\log$ 比根号快啊。

posted @ 2022-10-09 11:37  5k_sync_closer  阅读(4)  评论(0编辑  收藏  举报  来源