bzoj2589: Spoj 10707 Count on a tree II
Description
给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v),你需要回答u xor lastans和v这两个节点间有多少种不同的点权。其中lastans是上一个询问的答案,初始为0,即第一个询问的u是明文。
Input
第一行两个整数N,M。
第二行有N个整数,其中第i个整数表示点i的权值。
后面N-1行每行两个整数(x,y),表示点x到点y有一条边。
最后M行每行两个整数(u,v),表示一组询问。
数据范围是N<=40000 M<=100000 点权在int范围内
Output
M行,表示每个询问的答案。
Sample Input
8 2
105 2 9 3 8 5 7 7
1 2
1 3
1 4
3 5
3 6
3 7
4 8
2 5
3 8
Sample Output
4
4
HINT
暴力自重。。。
分析
一道树分块神题。
方法:深度分块
就是把树上隔根号n 的深度就分一块。
这样的分块方法不能保证块内点的个数不超过
n
\sqrt n
n 但是可以保证深度差与块个数不超过
n
\sqrt n
n 一般用于处理树上路径
思路
1.按深度分块,预处理每个点到块顶的答案。
若两点在同一块内,则可以直接暴力。
考虑不在块内的两点
u
,
v
u, v
u,v。并假设
u
u
u深度大。
可以直接采用
u
u
u的块顶
x
x
x到
v
v
v的答案。
这样剩下
u
u
u到
x
x
x上
n
\sqrt n
n个节点的信息维护。
可以暴力考虑每个点
2.对于一个点,产生贡献当且仅当这个点的权值没有在
x
,
v
x,v
x,v的路径上出现过。
所以现在问题转化为,一个点权值是否在某条路径上出现过。这个问题可以用经典的主席树来做,但是单次询问复杂度是
O
(
l
o
g
n
)
O(logn)
O(logn) 的,无法通过
算法:可持久化块状链表
块状链表这个东西简单来讲就是把序列分成
n
\sqrt n
n块,每块各自维护信息,并用链表的形式连接起来。
考虑可持久化这个东西。每一次改动仅仅会改动一个位置,也就是一个块上的信息。于是每进行一次修改,就暴力新建一个块,暴力继承之前的块,并修改那个点的信息。对于每个时间,记录下这个历史版本对应的块的编号。这样子的话,一次修改是
O
(
n
)
O(\sqrt n)
O(n),但是询问是
O
(
1
)
O(1)
O(1)的。
回到这道题,每个节点用块状链表维护当前点到根某种权值最低的深度,暴力的时候判断当前权值的深度是否大于lca即可。
代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
const int M = 210, N = 4e4 + 10;
int ri() {
char ch = getchar(); int x = 0, f = 1; for(;ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
for(;ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) - '0' + ch; return x * f;
}
int p[N][M], be[N], b[N], a[N], l[N], q[N], fa[N], de[N], A[M][N], r[N];
int pr[N], c[N], nx[N << 1], to[N << 1], rt[M], tot, Sz, Bs, tp, nn, kd;
void add(int u, int v) {to[++tp] = v; nx[tp] = pr[u]; pr[u] = tp;}
void adds(int u, int v) {add(u, v); add(v, u);}
int F(int x) {
int L = 1, R = nn, m;
for(;L != R; l[m = L + R >> 1] >= x ? R = m : L = m + 1);
return L;
}
struct B {
int a[M];
int &operator [] (int x) {return p[a[b[x]]][r[x]];}
void Ins(B l, int x, int d) {
int po = b[x], t = r[x];
std::memcpy(a, l.a, sizeof(a));
std::memcpy(p[++Sz], p[a[po]], sizeof(p[0]));
p[Sz][t] = d; a[po] = Sz;
}
}s[N];
struct {
int sz[N], ds[N], d[N];
void up(int u, int v) {sz[u] += sz[v]; if(sz[ds[u]] < sz[v]) ds[u] = v;}
void Dfs(int u, int c) {
d[u] = c; if(!ds[u]) return ; Dfs(ds[u], c);
for(int i = pr[u]; i; i = nx[i])
if(to[i] != fa[u] && to[i] != ds[u]) Dfs(to[i], to[i]);
}
int f(int u, int v) {
for(;d[u] != d[v]; u = fa[d[u]])
if(de[d[u]] < de[d[v]]) u ^= v ^= u ^= v;
return de[u] < de[v] ? u : v;
}
}lca;
int Dfs(int u, int f) {
fa[u] = f; s[u].Ins(s[f], a[u], de[u] = de[f] + 1); lca.sz[u] = 1;
q[++tp] = u; int md = de[u], p = tp;
for(int i = pr[u]; i; i = nx[i]) if(to[i] != f)
md = std::max(md, Dfs(to[i], u)), lca.up(u, to[i]);
if(md - de[u] >= Bs || p == 1) {
rt[++tot] = u;
for(int i = p; i <= tp; ++i) be[q[i]] = tot;
tp = p - 1; return de[u] - 1;
}
return md;
}
void Work(int u, int f, int *s) {
if(!c[a[u]]++) ++kd; s[u] = kd;
for(int i = pr[u]; i; i = nx[i]) if(to[i] != f) Work(to[i], u, s);
if(!--c[a[u]]) --kd;
}
int Sa(int u, int v) {
for(tp = kd = 0;u != v; u = fa[u]) {
if(de[u] < de[v]) u ^= v ^= u ^= v;
if(!c[q[++tp] = a[u]]) c[a[u]] = 1, ++kd;
}
for(kd += !c[a[u]];tp;) c[q[tp--]] = 0;
return kd;
}
int Sb(int u, int v) {
if(de[rt[be[u]]] < de[rt[be[v]]]) u ^= v ^= u ^= v;
int w = rt[be[u]], d = de[lca.f(u, v)]; kd = A[be[u]][v];
for(tp = 0;u != w; u = fa[u]) {
if(!c[a[u]] && s[w][a[u]] < d && s[v][a[u]] < d)
c[q[++tp] = a[u]] = 1, ++kd;
}
for(;tp;) c[q[tp--]] = 0;
return kd;
}
int main() {
int n = ri(), m = ri(); Bs = std::sqrt(n) - 1;
for(int i = 1;i <= n; ++i) b[i] = (i - 1) / Bs + 1, r[i] = i % Bs;
for(int i = 1;i <= n; ++i) l[i] = a[i] = ri();
std::sort(l + 1, l + n + 1);
l[nn = 1] =l[1]; for(int i = 2;i <= n; ++i) if(l[i] != l[i - 1]) l[++nn] = l[i];
for(int i = 1;i <= n; ++i) a[i] = F(a[i]);
for(int i = 1;i < n; ++i) adds(ri(), ri()); tp = 0;
Dfs(1, 0); lca.Dfs(1, 1);
for(int i = 1;i <= tot; ++i) Work(rt[i], 0, A[i]);
for(int la = 0;m--;) {
int u = ri() ^ la, v = ri();
printf("%d\n", la = be[u] == be[v] ? Sa(u, v) : Sb(u, v));
}
return 0;
}


227

被折叠的 条评论
为什么被折叠?



