边分治学习笔记
由于碰到了一道题CF1303G Sum of Prefix Sums,在处理路径时普通的点分治竟然不可暴力维护,而边分治却可以轻松维护,于是学了一学。
建议看本文时有着点分治学习基础。
边分治相较于点分治,在分支过程中将仅需要合并两颗子树,使得操作更加简单,也能维护更多的信息。
类似于点分治,边分治的思想就是找到树上的一条"重心边",然后将树分为两个大小尽量相同的子树递归完成操作。
但是重心边并不像树的重心那样优秀,比如一颗菊花图,这样常规的边分治退化,起不到折半缩小的作用,整体复杂度退化至\(O(n)\)
所以引入边分治中一个重要操作:树的三度化。
还是以菊花图为例:我们通过一个类似于构建哈夫曼树的过程重构树,将两个子节点连向一个新建的虚点,用虚点代替这两个子节点进行后续的加边操作,这样可以保证每个点的度数不大于\(3\),使得在边分支的过程中始终可以找到一条性能良好的重心边。
关键:在重构树时必须要将父节点的信息传递到虚点上,因为重构树会导致路径信息的改变,具体的说就是在新树上两个儿子间的路径可能仅通过虚点而不通过原来的父亲节点。
害我跳了一晚上代码,最后还是拍出来的
最后分享一下我的边分治板子
void reb(int x,int f){
int tmp=0;
id[x]=x;
for(int y:G[x])if(y!=f){
if(tmp){
int t=++tot;
Add(tmp,t,0),Add(t,tmp,0);
Add(t,y,1),Add(y,t,1);
tmp=t,id[t]=x;
}else Add(x,y,0),Add(y,x,0),tmp=x;
reb(y,x);
}
}
void getrt(int x,int f){
sz[x]=1;
erep(i,x){
int y=to[i];
if(y==f||mk[i>>1])continue;
getrt(y,x),sz[x]+=sz[y];
if(Mn>max(S-sz[y],sz[y])){
Mn=max(S-sz[y],sz[y]);
rt=i>>1;
}
}
}
void solve(int x,int sum){
if(sum==1)return ;
S=sum,Mn=1e9,getrt(x,0);
int a=to[rt<<1],b=to[rt<<1|1];
mk[rt]=1;
solve(b,sz[b]),solve(a,sz[a]);
}

浙公网安备 33010602011771号