其实我觉得以我的水平考场大概率用不出来树分块,但由于它太难写了,所以我还是写一下
题
树分块,顾名思义,和序列分块一样,把树也分成很多块,然后就可以根号复杂度处理一些问题,具体地,我们选\(\sqrt{n}\)个点,使得每个点的距离尽量为\(\sqrt{n}\),记录每个块往上一个块是哪里,处理询问时和序列分块一样,散块就暴力往父节点跳,整块则直接跳到上一个块,复杂度是正确的。
具体地,我们先把所有叶子节点选上,然后网上递归的时候判断子树内标记点距离当前点距离是否大于\(len\),如果大于就将当前点标记,讲未标记点分入块内时,为了保证块是链状结构,我们将两个标记点间的点全归到下方标记点代表的块内,即

(有数字的点为关键点)
考虑此题怎么做,首先看到颜色个数问题考虑维护值域01串,想到离散化后使用bitset维护每个块的答案,但由于bitset合并(块答案合并)复杂度是\(\frac{n}{w}\),如果每次询问时处理整块间答案,复杂度变成了\(O(\frac{qn}{w}\sqrt{n})\),显然这样会炸,但提前预处理复杂度就变成了\(\frac{n^{2}}{w}\),所以此题需要提前预处理
下面是一段加了很多注释的代码(因为怕被卡用的dfs序\(O(1)\)求lca)
点击查看代码
#include<bits/stdc++.h>
#define pb push_back
#define vt vector<int>
#define ll long long
#define pp pair<int,node>
#define N 40005
#define V 1000000
#define ull unsigned long long
#define se second
#define fi first
#define mk make_pair
#define lw(x) (x&(-x))
#define ls x<<1
#define rs x<<1|1
using namespace std;
const int Maxn=(1<<20);
const ll Minn=1e3;
const ll M=1e9+7;
const int inf=2e9+10;
const ll INF=1e18;
ll qm(ll x,ll y=M-2) {ll res=1;for(;y;x=x*x%M,y/=2) if(y&1) res=res*x%M;return res;
}
ll Mi(ll x,ll y) { return (x<y)?x:y; }
pair<ll,int> Mk(pair<ll,int> x,pair<ll,int> y) { return (x>y)?x:y; }
ll pw(ll x) { return x*x; }
void add(ll &x,ll y) { if((x+=y)>=M) x-=M; }
void add(ll &x,ll y,ll z) { x=(x+y*z%M)%M; }
void del(ll &x,ll y) { if((x-=y)<0) x+=M; }
inline int read() {char ch=getchar();int x=0,f=1;while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=x*10+ch-'0';ch=getchar();}return x*f;
}
int n,m,md[N],w[N],ww[N],len,dep[N],st[N],tot,idx,dfn[N],ct,qu[N<<1],lg[N<<1],dp[N<<1][22],Fa[N],fa[N],id[N];
vector<int> a[N];
bitset<N> bt[50][50],now;
void dfs(int x) {dfn[x]=++ct;qu[ct]=x;md[x]=dep[x]; //最近的关键点for(auto v:a[x]) {if(dep[v]) continue;dep[v]=dep[x]+1;fa[v]=x;dfs(v);qu[++ct]=x; //这里是st表求lcaif(md[v]>md[x]) md[x]=md[v];} if(md[x]-dep[x]>=len) id[x]=++idx,md[x]=dep[x]; //关键点
}
void dfs1(int x) {for(auto v:a[x]) {if(v==fa[x]) continue; if(id[v]) {int p=id[st[tot]],in=id[v];for(int i=v;i!=st[tot];i=fa[i]) bt[p][in].set(w[i]); //上一个关键点到这一个关键点(即块内)答案now=bt[p][in];for(int i=1;i<tot;i++) bt[id[st[i]]][in]=bt[id[st[i]]][p]|now; //祖先所有关键点到这个关键点答案Fa[v]=st[tot]; //这个块上一个块st[++tot]=v;} dfs1(v);if(id[v]) --tot;}
}
void build() { //lcafor(int i=1;i<=ct;i++) dp[i][0]=qu[i];for(int j=1;j<=20;j++) {for(int i=1;i+(1<<j)<=ct;i++) {int f1=dp[i][j-1],f2=dp[i+(1<<j-1)][j-1];dp[i][j]=dep[f1]<dep[f2]?f1:f2;}} lg[0]=-1;for(int i=1;i<=ct;i++) lg[i]=lg[i>>1]+1;
}
int lca(int x,int y) { //lcaif(dfn[x]>dfn[y]) swap(x,y);x=dfn[x];y=dfn[y];int d=lg[y-x+1];int f1=dp[x][d],f2=dp[y-(1<<d)+1][d];return dep[f1]<dep[f2]?f1:f2;
}
void solve() { cin>>n>>m; int x,y,ans=0;len=1000;for(int i=1;i<=n;i++) cin>>ww[i],w[i]=ww[i];int nn=unique(ww+1,ww+n+1)-ww-1;sort(ww+1,ww+nn+1);for(int i=1;i<=n;i++) w[i]=lower_bound(ww+1,ww+nn+1,w[i])-ww; //离散化for(int i=1;i<n;i++) {cin>>x>>y;a[x].pb(y);a[y].pb(x);} dep[1]=1;dfs(1);if(!id[1]) id[1]=++idx;st[++tot]=1;dfs1(1);build();while(m--) {int u,v;cin>>u>>v;u=u^ans;int lc=lca(u,v);now.reset();while(u!=lc&&!id[u]) now.set(w[u]),u=fa[u];while(v!=lc&&!id[v]) now.set(w[v]),v=fa[v]; //散块if(u!=lc) {x=u;while(dep[Fa[x]]>=dep[lc]) x=Fa[x];//整块跳if(x!=u) now|=bt[id[x]][id[u]];while(x!=lc) now.set(w[x]),x=fa[x]; } if(v!=lc) {x=v;while(dep[Fa[x]]>=dep[lc]) x=Fa[x];if(x!=v) now|=bt[id[x]][id[v]];while(x!=lc) now.set(w[x]),x=fa[x];} now.set(w[lc]);printf("%d\n",ans=now.count());}
}
signed main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t=1,c;while(t--) { solve(); }return 0;
}