点分树
拿板子说。
先考虑只询问一次的情况,直接点分治就行。
但是多次询问就不行了,时间复杂度会爆炸,所以我们考虑一个新的算法————淀粉鼠!
建树方法就是把每一次找到的重心连到上一次找到的重心上,形成一棵树,具体来说就是先对于整棵树找重心,然后把这个重心和它连的边全删了,再对这棵树上剩下的每个部分找重心,以此类推,就建好了。
那这棵树有什么优点?
- 这棵树的树高是 \(\log n\) 的,也就是说对于每个点我们暴力跳父亲最多跳 \(O(\log)\) 次就跳到根了。
- 这棵树上任意两个点 \(x\) 和 \(y\) ,它们的 \(lca\) 为重心划分子连通块时 \(x,y\) 首次分割开,所以说明这个点一定在原树中 \(x\) 到 \(y\) 的路径上
- 而对于一个 \(x\) ,它在点分树上的 \(lca\) 最多有 \(\log\) 个,所以计算 \(y\) 的话一般用容斥的方法计算出来
来看本题。
设 \(f_1(u,j)\) 表示在 \(u\) 子树中与 \(u\) 距离小于等于 \(j\) 的点的权值和, \(f_2(u,j)\) 表示在 \(u\) 父亲的子树中与 \(u\) 距离小于等于 \(j\) 的点的权值和
那么在一次查询 \((x,k)\) 中,一对虚树上的父子节点的贡献就是:
\[f(i,fa_i) = f_1(fa_i,k-dis(x,fa_i)) - f_2(i,k - dis(x, fa_i))
\]
不难发现的是有贡献的这一对父子节点只能在要查询节点 \(x\) 的上面,也就是只能是 \(x\) 的祖先,所以这一次查询的答案就是:
\[ans(x,k) = f_1(x,k) + \sum_{i\in fatree(x),fa_i\neq 0}f(i,fa_i)
\]
需要注意的是,\(x\) 自己子树里的贡献需要单独算
维护 \(f_1,f_2\) 可以动态开点,不难实现。
贴个代码
#include <vector>
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
const int inf = 1e9 + 7;
int n, m, rt, sum, tot;
int a[N];
int head[N];
bool vis[N];
struct Map { int to, nxt; } e[N << 1];
void add (int u, int v) {
e[++tot] = {v, head[u]};
head[u] = tot;
}
namespace LCA {
int dep[N], sz[N], son[N], fa[N], top[N];
void dfs1 (int u, int f) {
sz[u] = 1, fa[u] = f;
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (v == f) continue;
dep[v] = dep[u] + 1;
dfs1 (v, u), sz[u] += sz[v];
if (sz[son[u]] < sz[v]) son[u] = v;
}
}
void dfs2 (int u, int topf) {
top[u] = topf;
if (son[u]) dfs2 (son[u], topf);
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (v == fa[u] || v == son[u]) continue;
dfs2 (v, v);
}
}
int Lca (int x, int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) std::swap(x, y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
int dis (int x, int y) {
return dep[x] + dep[y] - 2 * dep[Lca (x, y)];
}
}
struct BIT {
int n;
vector <int> sum;
int lb (int x) { return x & (-x); }
void build (int m) { sum.resize ((n = m) + 2); }
void add (int x, int k) {
x++;
for (int i = x; i <= n; i += lb(i))
sum[i] += k;
return;
}
int ask (int x) {
int res = 0; x++;
x = min (x, n);
for (int i = x; i; i -= lb(i))
res += sum[i];
return res;
}
} t1[N], t2[N];
int fa[N], sz[N], mxs[N];
void getrt (int u, int fa) {
sz[u] = 1, mxs[u] = 0;
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (v == fa || vis[v]) continue;
getrt (v, u), sz[u] += sz[v];
mxs[u] = max (mxs[u], sz[v]);
}
mxs[u] = max (mxs[u], sum - mxs[u]);
if (mxs[u] < mxs[rt]) rt = u;
}
void solve (int x, int f) {
int now = sum;
t1[x].build (now + 1), t2[x].build (now + 1);
vis[x] = true, fa[x] = f;
for (int i = head[x]; i; i = e[i].nxt) {
int v = e[i].to;
if (vis[v]) continue;
sum = (sz[v] > sz[x] ? now - sz[x] : sz[v]);
mxs[rt = 0] = inf;
getrt(v, 0), solve (rt, x);
}
}
void modify (int x, int k) {
t1[x].add (0, k);
for (int i = x; fa[i]; i = fa[i]) {
int d = LCA::dis(x, fa[i]);
t1[fa[i]].add (d, k);
t2[i].add (d, k);
}
}
int query (int x, int k) {
int res = t1[x].ask (k);
for (int i = x; fa[i]; i = fa[i]) {
int d = LCA::dis (fa[i], x);
if (d > k) continue;
res += t1[fa[i]].ask (k - d) - t2[i].ask (k - d);
}
return res;
}
int main () {
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> a[i];
for (int i = 2, u, v; i <= n; ++i)
cin >> u >> v, add (u, v), add (v, u);
LCA::dfs1 (1, 0), LCA::dfs2 (1, 1);
sum = n, mxs[rt = 0] = inf;
getrt (1, 0), solve (rt, 0);
for (int i = 1; i <= n; i++)
modify (i, a[i]);
for (int i = 1, opt, x, y, lastans = 0; i <= m; i++) {
cin >> opt >> x >> y;
x ^= lastans; y ^= lastans;
if (opt == 1) modify (x, y - a[x]), a[x] = y;
else lastans = query (x, y), cout << lastans << '\n';
}
return 0;
}

浙公网安备 33010602011771号