P6177题解/树分块学习笔记
没有 Top Cluster 树分块的题解,这里补一发。
定义
-
树簇:树中的一个联通块,满足其与外界相邻的节点不超过
2 个。 -
界点:树簇与外界相邻的节点。这里钦定深度较浅的是上界点,深度较深的是下界点(上下界点一定是祖孙关系)。
-
收缩树:考虑将树划分成若干个树簇,满足任意一条边在且仅在一个树簇中,任意一个点至少在一个树簇中。将每个树簇看成一个点后形成的新的树为收缩树。
-
簇路径:树簇中上界点到下界点之间的路径。
考虑将原树划分成
注意,整块整体处理的是簇路径而不是整个树簇。
实现
考虑对原树进行DFS,标记上下界点。
节点
对于上界点
这个过程略显粗糙,但树簇大小的浮动是在常数级别的,可以接受。
具体实现的时候,只需要在每个上界点打个 tag,然后下传标记即可。
注意,由于上界点一定属于多个树簇,所以一般把上界点
划分树簇且建收缩树的过程如下:
il void Merge(int x,int y,int i,int ed){
L[++N]=x,R[N]=y;
for(;i^ed;i=e1[i].to)
if(e1[i].v^fa1[x]) tag[e1[i].v]=N;//标记tag
}
void init1_dfs(int fa,int x){
int cnt=0;
fa1[x]=fa,dep1[x]=dep1[fa]+1,res[x]=1;
for(int i=head1[x];i;i=e1[i].to)
if(e1[i].v^fa){
init1_dfs(x,e1[i].v),res[x]+=res[e1[i].v];
if(Clu[e1[i].v]) cnt++,Clu[x]=Clu[e1[i].v];
}
if(cnt>1||res[x]>=B||x==1){
//若满足三个条件之一,标记上界点,并划分子树
Clu[x]=x,res[x]=0;
int sum=1,id=0,li=head1[x];
for(int i=head1[x];i;i=e1[i].to)
if(e1[i].v^fa){
if(sum+res[e1[i].v]>B||(id&&Clu[e1[i].v]))
Merge(x,id,li,i),sum=0,id=0,li=i;
//划分出新树簇,并更新子树大小,和下界点编号
if(Clu[e1[i].v]) id=Clu[e1[i].v];
sum+=res[e1[i].v];
}
Merge(x,id,li,0);//最后一部分也要划分成新树簇
}
}
void down_dfs(int fa,int x,int c=N){
bl[x]=c;bool fl=0;
for(int i=head1[x];i;i=e1[i].to)
if(e1[i].v^fa) fl=1,down_dfs(x,e1[i].v,tag[e1[i].v]?tag[e1[i].v]:c);
if(!fl&&!R[c]) R[c]=x;
//如果该树簇没有下界点,就随便选择一个叶节点作下界点
if(fa1[x]&&!Vis[bl[x]]&&(bl[x]^bl[fa1[x]])){
Vis[bl[x]]=1;//Vis数组保证 bl[x] 与其父节点不重复连边
addedge2(bl[x],bl[fa1[x]]);
addedge2(bl[fa1[x]],bl[x]);
}
}
void init2_dfs(int fa,int x){
fa2[x]=fa,dep2[x]=dep2[fa]+1;
for(int i=head2[x];i;i=e2[i].to)
if(e2[i].v^fa) init2_dfs(x,e2[i].v);
}
.....
int main(){
.....
init1_dfs(0,1),L[++N]=1,R[N]=1,bl[1]=N;
down_dfs(0,1),init2_dfs(0,N);
return 0;
}
本题做法
考虑对于本题树分块的做法(以下称树簇为块)。
对于任意两个块
对于零散块部分,显然时间复杂度是
所以该算法的时间复杂度为
完整代码如下:
#include<bits/stdc++.h>
#define ull unsigned long long
#define il inline
using namespace std;
const int w=(1<<16)-1;
const int maxn=40010;
const int maxqn=310;
const ull S=18446744073709551615ull;
il int read(){
int x=0;
char c=getchar();
for(;!(c>='0'&&c<='9');c=getchar());
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+c-'0';
return x;
}
struct Hsh{
int id,x;
bool operator <(Hsh a)const{return x<a.x;}
}b[maxn];
int nn,pc[1<<16];
//////////bitset
struct Bitset{
ull a[maxn/64+2];
il Bitset operator |(Bitset x)const{for(int i=0;i<=nn;i++) x.a[i]|=a[i];return x;}
il int Count(int cnt=0)const{
for(int i=0;i<=nn;i++)
cnt+=pc[a[i]&w]+pc[(a[i]>>16)&w]+pc[(a[i]>>32)&w]+pc[(a[i]>>48)&w];
return cnt;
}
}col[maxqn],data[maxqn][maxqn];
il void Add(Bitset &x,int i){x.a[i-1>>6]|=(1ull<<(i-1&63));}
il void clear(Bitset &x){for(int i=0;i<=nn;i++)x.a[i]=0;}
/////////////
struct edge{
int v,to;
}e1[maxn<<1],e2[maxqn<<1];
int head1[maxn],head2[maxqn];
int ecnt1=1,ecnt2=1;
il void addedge1(int u,int v){
e1[++ecnt1].v=v,e1[ecnt1].to=head1[u],head1[u]=ecnt1;
}
il void addedge2(int u,int v){
e2[++ecnt2].v=v,e2[ecnt2].to=head2[u],head2[u]=ecnt2;
}
int a[maxn],n,m,B,N;
int fa1[maxn],dep1[maxn];
int res[maxn],tag[maxn],Clu[maxn];
bool Vis[maxqn];
int fa2[maxqn],dep2[maxqn];
int L[maxqn],R[maxqn];
int bl[maxn];
///////////划分树簇
il void Merge(int x,int y,int i,int ed){
L[++N]=x,R[N]=y;
for(;i^ed;i=e1[i].to)
if(e1[i].v^fa1[x]) tag[e1[i].v]=N;
}
void init1_dfs(int fa,int x){
int cnt=0;
fa1[x]=fa,dep1[x]=dep1[fa]+1,res[x]=1;
for(int i=head1[x];i;i=e1[i].to)
if(e1[i].v^fa){
init1_dfs(x,e1[i].v),res[x]+=res[e1[i].v];
if(Clu[e1[i].v]) cnt++,Clu[x]=Clu[e1[i].v];
}
if(cnt>1||res[x]>=B||x==1){
Clu[x]=x,res[x]=0;
int sum=1,id=0,li=head1[x];
for(int i=head1[x];i;i=e1[i].to)
if(e1[i].v^fa){
if(sum+res[e1[i].v]>B||(id&&Clu[e1[i].v]))
Merge(x,id,li,i),sum=0,id=0,li=i;
if(Clu[e1[i].v]) id=Clu[e1[i].v];
sum+=res[e1[i].v];
}
Merge(x,id,li,0);
}
}
void down_dfs(int fa,int x,int c=N){
bl[x]=c;bool fl=0;
for(int i=head1[x];i;i=e1[i].to)
if(e1[i].v^fa) fl=1,down_dfs(x,e1[i].v,tag[e1[i].v]?tag[e1[i].v]:c);
if(!fl&&!R[c]) R[c]=x;
if(fa1[x]&&!Vis[bl[x]]&&(bl[x]^bl[fa1[x]])){
Vis[bl[x]]=1;
addedge2(bl[x],bl[fa1[x]]);
addedge2(bl[fa1[x]],bl[x]);
}
}
void init2_dfs(int fa,int x){
fa2[x]=fa,dep2[x]=dep2[fa]+1;
for(int i=head2[x];i;i=e2[i].to)
if(e2[i].v^fa) init2_dfs(x,e2[i].v);
}
////////////
//////预处理两两块之间的bitset
void initd_dfs(int rt,int fa,int x,Bitset t){
t=t|col[x],data[rt][x]=t;
for(int i=head2[x];i;i=e2[i].to)
if(e2[i].v^fa) initd_dfs(rt,x,e2[i].v,t);
}
//////////
///////零散块内的暴力
il Bitset Query(int x,int y){
Bitset ans;clear(ans);
if(dep1[x]<dep1[y]) swap(x,y);
while(dep1[x]>dep1[y]) Add(ans,a[x]),x=fa1[x];
while(x^y){
Add(ans,a[x]),x=fa1[x];
Add(ans,a[y]),y=fa1[y];
}Add(ans,a[x]);
return ans;
}
////////////
il int Solve(int x,int y){
Bitset ans;clear(ans);
int u=bl[x],v=bl[y];
if(dep2[u]<dep2[v]) swap(u,v),swap(x,y);
if(u==v) return Query(x,y).Count();
ans=Query(x,L[u]),u=fa2[u];
int uu=u;
while(dep2[uu]-1>dep2[v]) uu=fa2[uu];
if(dep2[uu]-1==dep2[v])ans=ans|data[u][uu],u=fa2[uu];
if(u^v){
ans=ans|Query(y,L[v]),v=fa2[v];
if(dep2[u]>dep2[v]) ans=ans|col[u],u=fa2[u];
}
else return (ans|Query(R[v],y)).Count();
if(u==v) return ans.Count();
int u_=u,v_=v;
//注意,lca(u,v) 对应的块实际上是没有走过簇路径的
//所以需要分别或上 u,v 两侧路径的bitset
while(fa2[u_]^fa2[v_]) u_=fa2[u_],v_=fa2[v_];
return (ans|data[u][u_]|data[v][v_]).Count();
}
int main(){
n=read(),m=read(),B=1000;
for(int i=1;i<=n;i++){
a[i]=read();
b[i]=Hsh{i,a[i]};
}
sort(b+1,b+1+n);int val=0;
b[0].x=-1;
for(int i=1;i<=n;i++){
if(b[i].x^b[i-1].x) val++;
a[b[i].id]=val;
}nn=val/64;
for(int i=1;i<n;i++){
int x=read(),y=read();
addedge1(x,y),addedge1(y,x);
}
for(int i=1;i<(1<<16);i++) pc[i]=pc[i>>1]+(i&1);
init1_dfs(0,1),L[++N]=1,R[N]=1,bl[1]=N;
down_dfs(0,1),init2_dfs(0,N);
for(int i=1;i<=N;i++){
int x=R[i];Add(col[i],a[x]);
while(x^L[i]) x=fa1[x],Add(col[i],a[x]);
//处理每个块簇路径对应的bitset
}Bitset t;clear(t);
for(int i=1;i<=N;i++)
initd_dfs(i,0,i,t);
int x,y,lans=0;
while(m--){
x=read()^lans,y=read();
printf("%d\n",lans=Solve(x,y));
}
return 0;
}
Top Cluster 树分块的优劣
-
首先,该算法比起一般树分块算法来说,拥有严格的
O(n\sqrt n) 时间复杂度,其次,每个块只有两个端点的特性,使得任意在序列上能维护的信息都能以相同的时间复杂度在树上维护。 -
但是这个玩意…………真的超级难写啊!而且常数也是真的大qwq