动态点分治复习
算法思路
大概就是让一个点管辖一堆点,并构成父子关系,以维护一些东西。发现使重心为管辖点最好。点分树有几个性质,树高logn,可以暴力爬树维护。并且注意这里的父子关系可能在原树上离得很远,要注意区分。
关于写法
写两棵树的全局变量非常麻烦,可以封装结构体。注意有时候tree1和tree2要相互调用,可以这样写:
int dis(int,int);
struct tree1{
...
void add(int u,int v){
...
}
void modify(int u,int w){
... dis(u,fa[now]) ...
}
}t1;
struct tree2{
...
void get_dis(int a,int b){
...
}
void dfs(int now){
... t1.add(fa[now],now); ...
}
}t2;
int dis(int a,int b){
return t2.get_dis(a,b);
}
这样调试会方便一点,因为可以在两个结构体里开一样的变量名。
核心代码
inline void getrt(int u,int pre){
sz[u]=1,mx[u]=0;//注意每次都要初始化
for(int i=head[u];i;i=edge[i].nex){
int v=edge[i].v;
if(vis[v]||v==pre) continue;
getrt(v,u);sz[u]+=sz[v];
mx[u]=max(mx[u],sz[v]);
}
mx[u]=max(mx[u],sum-sz[u]);
if(mx[u]<mx[rt]) rt=u;
}
inline void work(int u,int pre){
vis[u]=1;fa[u]=pre;
for(int i=head[u];i;i=edge[i].nex){
int v=edge[i].v;
if(vis[v]) continue;
sum=sz[v];mx[0]=sz[v];rt=0;
getrt(v,0);t2.add(u,rt,v);
work(rt,u);
}
}
做题思路
维护查询的东西,跟点分治差不多。注意树结构不变。修改,考虑对一些管辖点维护的东西的变化,可以发现这些点就是点分树上到根的路径上的点,暴力爬树修改即可。
举个例子:
长度为k的链权值之和
用动态开点线段树每个点维护经过这个点的长度为k的链长之和,把每个点可以接上的长度为k的链有多少个存起来(总状态数(O(nlog n))),每次爬树用线段树区间修改(注意减去重复的部分),复杂度(nlog^2n)。注意树上联通块大小每次至少减半,于是线段树总共的待修改区间是(O(n))的,这部分常数很小(只要你范围写对)。