浅记树分块
其实我觉得以我的水平考场大概率用不出来树分块,但由于它太难写了,所以我还是写一下
树分块,顾名思义,和序列分块一样,把树也分成很多块,然后就可以根号复杂度处理一些问题,具体地,我们选\(\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表求lca
if(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() { //lca
for(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) { //lca
if(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;
}

浙公网安备 33010602011771号