【题解】[AHOI2022] 钥匙
提供一个简单树剖/差分的做法。
我们先考虑 \(A\) 性质怎么做,令每种颜色的钥匙和箱子分别为 \((x,y)\),那么会对起点在 \(x\) 一端,终点在 \(y\) 一端的询问产生贡献。所以我们对询问离线,问题转化为矩阵加单点查询,直接扫描线即可。
没有特殊性质,但我们延续上面的思路,考虑对同颜色的钥匙和箱子 \((x,y)\) 计算贡献。由于钥匙 \(\le 5\),所以所有 \((x,y)\) 最多是 \(5n\)。
我们依次考虑每种颜色。但并不是所有的 \((x,y)\) 都满足条件,观察一下不难发现,如果我们将钥匙记为 \(+1\),箱子记为 \(-1\),其余记为 \(0\),那么树链 \(x\to y\) 的和为 \(0\) 是产生贡献的必要条件。
但是这样连样例都过不去,答案会偏大,显然有贡献重复统计了。比如 \(1,-1,1,-1\),其中 \((1,2)\) 和 \((1,4)\) 被计算了两次。
如果数对 \((x,y)\) 在路径 \(x\to y\) 上有 \(z\) 使得 \((x,z)\) 产生贡献,那么 \(y\) 就不产生贡献。那么我们固定 \(x\),将所有可能的 \(y\) 按到 \(x\) 的距离排序,依次考虑,如果产生贡献就在 \(y\) 单点加,每次统计 \((x,y)\) 时先判断一下是否路径和为 \(0\)。
可以直接用两个树链剖分维护,时间复杂度 \(\mathcal{O}(5n\log^2 n + m\log m)\),由于树剖和树状数组的常数都很小,可以无压力通过。
事实上,我们维护的是单点加,树链查询,直接树上差分树状数组维护,时间复杂度 \(\mathcal{O}((n+m)\log n)\)。
树剖写的习惯,下面是树剖代码。
#define N 500005
int n, m, c[N], d[N], dfn[N], fa[N], top[N], sz[N], son[N], mat[N], ds[N], idx, w[N];
inline void add(int x,int y){for(; x <= n; x += x & -x)c[x] += y;}
inline int ask(int x){int sum = 0; for(; x; x -= x & -x)sum += c[x]; return sum;}
inline void Add(int x,int y){for(; x <= n; x += x & -x)w[x] += y;}
inline int Ask(int x){int sum = 0; for(; x; x -= x & -x)sum += w[x]; return sum;}
inline int get(int l,int r){return ask(r) - ask(l - 1);}
inline int Get(int l,int r){return Ask(r) - Ask(l - 1);}
vector<int>a[N], b[N], e[N]; vector<Pr> u[N], v[N];
void dfs(int x,int f){
d[x] = d[fa[x] = f] + (sz[x] = 1);
go(y, e[x])if(y != f){
dfs(y, x), sz[x] += sz[y];
if(sz[y] > sz[son[x]])son[x] = y;
}
}
void dfs2(int x,int tp){
top[x] = tp, mat[dfn[x] = ++idx] = x;
if(!son[x])return;
dfs2(son[x], tp);
go(y, e[x])if(y != fa[x] && y != son[x])dfs2(y, y);
}
inline int lca(int x,int y){
while(top[x] != top[y]){
if(d[top[x]] < d[top[y]])swap(x, y);
x = fa[top[x]];
}
return d[x] < d[y] ? x : y;
}
inline int dis(int x,int y){return d[x] + d[y] - d[lca(x, y)] * 2;}
int query(int x,int y){
int sum = 0;
while(top[x] != top[y]){
if(d[top[x]] < d[top[y]])swap(x, y);
sum += get(dfn[top[x]], dfn[x]), x = fa[top[x]];
}
if(d[x] > d[y])swap(x, y);
sum += get(dfn[x], dfn[y]);
return sum;
}
int Query(int x,int y){
int sum = 0;
while(top[x] != top[y]){
if(d[top[x]] < d[top[y]])swap(x, y);
sum += Get(dfn[top[x]], dfn[x]), x = fa[top[x]];
}
if(d[x] > d[y])swap(x, y);
sum += Get(dfn[x], dfn[y]);
return sum;
}
int calc(int x,int k){
while(d[top[x]] > k)x = fa[top[x]];
return mat[dfn[x] - d[x] + k];
}
#define ck(x, y) (dfn[x] <= dfn[y] && dfn[x] + sz[x] > dfn[y])
inline void ins(int l,int r,int L,int R){
if(l > r || L > R)return;
u[l].pb(mp(L, R)), v[r + 1].pb(mp(L, R));
}
void ins(int x,int y){ // x to y
if(ck(x, y)){
int z = calc(y, d[x] + 1);
ins(1, dfn[z] - 1, dfn[y], dfn[y] + sz[y] - 1);
ins(dfn[z] + sz[z], n, dfn[y], dfn[y] + sz[y] - 1);
}
else if(ck(y, x)){
int z = calc(x, d[y] + 1);
ins(dfn[x], dfn[x] + sz[x] - 1, 1, dfn[z] - 1);
ins(dfn[x], dfn[x] + sz[x] - 1, dfn[z] + sz[z], n);
}
else ins(dfn[x], dfn[x] + sz[x] - 1, dfn[y], dfn[y] + sz[y] - 1);
}
struct node{
int l, r, id;
bool operator<(const node o)const{return l < o.l;}
}q[N << 1];
int ed[N << 1];
int main() {
read(n, m);
rp(i, n){
int x, y; read(x, y);
if(1 == x)a[y].pb(i);
else b[y].pb(i);
}
rp(i, n - 1){
int x, y;
read(x, y);
e[x].pb(y), e[y].pb(x);
}
dfs(1, 0), dfs2(1, 1);
rp(i, n)if(si(a[i]) && si(b[i])){
go(x, a[i])add(dfn[x], 1);
go(x, b[i])add(dfn[x], -1);
go(x, a[i]){
go(y, b[i])ds[y] = dis(x, y);
sort(b[i].begin(), b[i].end(), [&](int x,int y){return ds[x] < ds[y];});
vector<int>dl;
go(y, b[i]){
if(0 == query(x, y) && !Query(x, y)){
Add(dfn[y], 1), dl.pb(y), ins(x, y);
}
}
go(y, dl)Add(dfn[y], -1);
}
go(x, a[i])add(dfn[x], -1);
go(x, b[i])add(dfn[x], 1);
}
memset(c, 0, sizeof(c));
rp(i, m)read(q[i].l, q[i].r), q[i].id = i, q[i].l = dfn[q[i].l], q[i].r = dfn[q[i].r];
sort(q + 1, q + m + 1);
int j = 1;
rp(i, n){
go(x, u[i])add(x.fi, 1), add(x.se + 1, -1);
go(x, v[i])add(x.fi, -1), add(x.se + 1, 1);
while(j <= m && q[j].l == i)ed[q[j].id] = ask(q[j].r), j++;
}
rp(i, m)printf("%d\n", ed[i]);
return 0;
}