P6177 Count on a tree II/【模板】树分块
树分块板题。
树分块有很多种形式,可以按结点个数分块,深度分块...各有优劣。
具体可以看这里。
这里用的是按结点个数分块。
那么就可以这样来处理每一个询问:
设 (u`) 为 (u) 的块的根 ,(v') 为 (v) 的根,(t1) 为 (u) 的祖先中最接近 (lca) 的根,(t2) 为 (v) 的祖先中最接近 (lca) 的根。
于是我们的路径就可以划分成 (A=(u,u')) ,(B=(v,v`)) ,(C=(u`,t1)) ,(D=(v`,t2)),(E=(t1,lca)),(F=(t2,lca)) 。
对于 (C,D) 我们可以直接预处理出每一个块到上一个块的答案,然后询问的时候暴力跳即可,因为最多有(sqrt{n}) 个块。
然后对于 (A,B,E,F) ,我们都可以直接在每次询问的时候暴力往上跳即可,这部分的复杂度不会超过 (sqrt{n}) 。
于是对于最后我们求出来的这 6 个集合取一个并集即可,使用 bitset 优化。
时间复杂度 (O((n+m)(sqrt{n}+frac{c}{w}))) 。
代码:
#include<bits/stdc++.h>
using namespace std;
template <typename T>
inline void read(T &x){
x=0;char ch=getchar();bool f=false;
while(!isdigit(ch)){if(ch=='-'){f=true;}ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return ;
}
template <typename T>
inline void write(T x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10^48);
return ;
}
const int N=4e4+5,C=4e4,B=205;
int n,m,type,las,c[N];
int head[N],nex[N<<1],to[N<<1],idx;
int dep[N],key[N],keyid[N],bl[N],sta[N],top,tot;
int fa[N],Fa[N];
bool vis[N];
void add(int u,int v){
nex[++idx]=head[u];
to[idx]=v;
head[u]=idx;
return ;
}
bitset<C> G[N/B+5][N/B+5],tmp;//块与块之间的根节点的答案和中间变量
void dfs(int x){
int now=top;dep[x]=dep[fa[x]]+1;
for(int i=head[x];i;i=nex[i]){
int y=to[i];
if(y==fa[x]) continue;
fa[y]=x;
dfs(y);
if(top-now>=B){//如果可以分成一个块
key[++tot]=x;//key指当前块的根
if(!keyid[x]) keyid[x]=tot;//keyid指该根节点所属的最初块编号
while(top>now) bl[sta[top--]]=tot;//把所有点所属的块编号标记
}
}
sta[++top]=x;//加入
return ;
}
int b[N];
int main(){
read(n),read(m);
for(int i=1;i<=n;i++) read(c[i]),b[i]=c[i];
sort(b+1,b+n+1);
int Idx=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++) c[i]=lower_bound(b+1,b+Idx+1,c[i])-b;
for(int i=1;i<n;i++){
int u,v;
read(u),read(v);
add(u,v);
add(v,u);
}
dfs(1);
if(!tot) tot++;//如果分不出来,那就强行分
if(keyid[key[tot]]==tot) keyid[key[tot]]=0;//如果最后一个块的根节点属于其本身(而不是属于0),那么置成0
key[tot]=1;keyid[1]=tot;//把根节点置成最后一个块的根,根节点属于最后一个块
while(top) bl[sta[top--]]=tot;//划分最后一个块
for(int i=1;i<=tot;i++){//预处理
if(vis[key[i]]) continue;//预处理的是节点而不是块
vis[key[i]]=true;//标记
tmp.reset();//重置 中间变量
for(int u=key[i];u;u=fa[u]){//从当前点一直跳到根节点
tmp[c[u]]=1;//把一路到根的颜色置成 1
if(keyid[u]){
if(!Fa[key[i]]&&u!=key[i]) Fa[key[i]]=u;//新树上的父亲:Fa
G[keyid[key[i]]][keyid[u]]=tmp; //当前块到路径上每一个块的根节点
}
}
}
while(m--){
tmp.reset();
int u,v,x,y;read(u),read(v);u^=las;x=u,y=v;
while(key[bl[x]]!=key[bl[y]]){
if(dep[key[bl[x]]]>dep[key[bl[y]]]){
if(x==u) while(x!=key[bl[u]]) tmp[c[x]]=1,x=fa[x];//若是第一次跳先暴力跳到关键点,则除非到了根,不然一直往上跳
else x=Fa[x];//否则跳一整块
}
else{//同理
if(y==v) while(y!=key[bl[v]]) tmp[c[y]]=1,y=fa[y];
else y=Fa[y];
}
}
if(keyid[x]) tmp|=G[keyid[key[bl[u]]]][keyid[x]];//大块之间的贡献
if(keyid[y]) tmp|=G[keyid[key[bl[v]]]][keyid[y]];//右边的路径也一样
while(x!=y){//在同一块
if(dep[x]>dep[y]) tmp[c[x]]=1,x=fa[x];//一步一步跳
else tmp[c[y]]=1,y=fa[y];//同理
}
tmp[c[x]]=1;//记得根节点LCA处
int Ans1=tmp.count(),Ans2=(~tmp)._Find_first();las=Ans1;
write(Ans1),putchar('
');
}
return 0;
}