FOJ 花园 题解
题面
给定一个花园,有\(N\)个温室,构成一棵树。每个温室种一种花,第\(i\)个温室种类为\(T_i\)。给定\(Q\)个操作,操作为下面两种形式之一:
- \(C x t\):表示在温室\(x\)中的花种类变为\(t\)
- \(Q x y t\):表示查询\(x\)到\(y\)的路径中种类为\(t\)的花出现几次
操作必须在线。每次\(x,y,t\)要亦或上次查询的答案。
\(1≤N,Q≤2×10^5\),\(T_i\)和\(t\)均为int范围内的整数。
题解
\(t\)范围过大,且要求在线,不能离散化,那就用map映射。
查询操作让我们想到了LCA。设\(x\)和\(y\)的LCA为\(z\),那么总次数=\(x\)到\(z\)的次数+\(y\)到\(z\)的次数。凭经验,我们设\(s_{x,t}\)表示\(x\)到根节点中种类为\(t\)的花的个数。那么答案=\(s_{x,t}+s_{y,t}-2×s_{z,t}+(T_z==t)\)。
发现,在修改操作之后,以\(x\)为根的子树中所有节点\(i\)的\(s_{i,T_x}\)减1,\(s_{i,t}\)加1。是不是有点线段树的感觉?但这里的“区间”是一整棵子树,而不是抽象的“线段”。
子树转化为区间的常用技巧是求dfs序,因为在dfs序中,一棵子树中的所有点一定连在一起。此题可以使用这种方法。按照dfs序的顺序维护所有节点,每次\(C\)操作修改一段连续的区间。但我们又面临着一个问题:要求维护的信息(即\(s\))会根据\(x\)的变化而变化,一棵线段树无法解决问题。
那么就对每个节点再开一棵线段树,变成线段树套线段树。每个节点的线段树中以花的种类作为“下标”,维护\(s\)。利用动态开点,空间可以承受。然而,稍加分析会发现:这时懒惰标记向下传递会十分困难,因为其不满足“区间可加性”(PS:对线段树,尤其是懒惰标记的实现而言,这一点非常重要)。
再换一种方法。我们对每个种类开一棵线段树,“下标”为dfs序,权值为\(s\)。再回顾一边两种操作。\(C\)操作,只需分别在\(T_x\)和\(t\)的树中区间修改(此时懒惰标记可以“加”);\(Q\)操作,在\(t\)的树中单点查询。于是问题解决。
Code
#include<cstdio>
#include<cmath>
#include<map>
#include<algorithm>
using namespace std;
int read() {
long long x = 0, f = 1; char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') {x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}
return x * f;
}
const int N = 2e5 + 5;
const int M = 1e7 + 5;
const int H = 19;
int n, m, h, w[N], root[N << 1], idx, mpt;
int head[N], nxt[N << 1], ver[N << 1], tot;
int dfn, l[N], r[N], d[N], f[N][H];
map<int, int> mp;
struct Tree {
int lc, rc, cnt;
} t[M];
void Add(int x, int y) {
nxt[++tot] = head[x]; head[x] = tot; ver[tot] = y;
}
void pushdown(int p) {
if (!t[p].cnt) return ;
if (!t[p].lc) t[p].lc = ++idx;
if (!t[p].rc) t[p].rc = ++idx;
t[t[p].lc].cnt += t[p].cnt; t[t[p].rc].cnt += t[p].cnt;
t[p].cnt = 0;
}
void Insert(int dl, int dr, int v, int l, int r, int &p) {
if (!p) p = ++idx;
if (dl <= l && r <= dr) {
t[p].cnt += v; return ;
}
int mid = l + r >> 1;
pushdown(p);
if (dl <= mid) Insert(dl, dr, v, l, mid, t[p].lc);
if (dr > mid) Insert(dl, dr, v, mid + 1, r, t[p].rc);
}
void dfs(int x) {
l[x] = ++dfn;
for (int i = head[x]; i; i = nxt[i]) {
int y = ver[i];
if (d[y]) continue;
d[y] = d[x] + 1;
f[y][0] = x;
for (int j = 1; j <= h; j++) f[y][j] = f[f[y][j - 1]][j - 1];
dfs(y);
}
r[x] = dfn;
Insert(l[x], r[x], 1, 1, n, root[w[x]]);
}
int lca(int x, int y) {
if (d[x] < d[y]) swap(x, y);
for (int i = h; i >= 0; i--)
if (d[f[x][i]] >= d[y]) x = f[x][i];
if (x == y) return x;
for (int i = h; i >= 0; i--)
if (f[x][i] != f[y][i]) {
x = f[x][i]; y = f[y][i];
}
return f[x][0];
}
int Query(int x, int l, int r, int &p) {
if (!p) p = ++idx;
if (l == r) return t[p].cnt;
int mid = l + r >> 1;
pushdown(p);
if (x <= mid) return Query(x, l, mid, t[p].lc);
return Query(x, mid + 1, r, t[p].rc);
}
int main() {
char opt[5];
int ans = 0;
n = read(); m = read(); h = (int)log2(n) + 1;
for (int i = 1; i <= n; i++) {
w[i] = read();
if (mp.find(w[i]) == mp.end()) mp[w[i]] = ++mpt; w[i] = mp[w[i]];
}
for (int i = 1; i < n; i++) {
int x = read(), y = read();
Add(x, y); Add(y, x);
}
d[1] = 1; dfs(1);
while (m--) {
scanf("%s", opt);
if (opt[0] == 'C') {
int x = read() ^ ans, t = read() ^ ans;
if (mp.find(t) == mp.end()) mp[t] = ++mpt; t = mp[t];
Insert(l[x], r[x], -1, 1, n, root[w[x]]);
Insert(l[x], r[x], 1, 1, n, root[w[x] = t]);
}
else {
int x = read() ^ ans, y = read() ^ ans, t = read() ^ ans;
if (mp.find(t) == mp.end()) mp[t] = ++mpt; t = mp[t];
int z = lca(x, y);
ans = Query(l[x], 1, n, root[t]) + Query(l[y], 1, n, root[t]) - 2 * Query(l[z], 1, n, root[t]) + (w[z] == t);
printf("%d\n", ans);
}
}
return 0;
}