T3 题解
$\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 序搞下来,维护两个东西:
- 区间大于等于某个数的数的和。
- 区间大于等于某个数的数的个数。
两部分都是平凡的。
要是别的题,题解应该结束了。但这是考试题,所以要讲一下怎么维护。
树状数组
考虑将询问按 $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。
吉司机线段树
跑了 2.20s。
Update:可以卡成 $O(n^2)$,寄了。
后记
做法 | 用时 | 在线/离线 |
---|---|---|
树状数组 | 998ms | 离线 |
主席树 | 4.13s | 离线 |
分块 | 2.5s | 在线 |
莫队+值域分块 | 3.26s | 离线 |
吉司机线段树 | 2.20s | 在线 |
统一 scanf
输入。
代码格式化过。
主席树板题卡主席树。
为什么卡空间啊。
为什么根号比 $\log$ 快啊。
为什么根号 $\log$ 比根号快啊。