用途
统计所有树上两点的路径的信息,常规是 O(n2) ,用此算法可已达到 O(R×nlog2n) (R为维护的数据结构时间复杂度)。
主要思路
通过寻找树的重心,把树分成若干块(重心的子树),再分治处理每个子树。
(重心是一个结点使它的子树中结点个数最大的值最小)
由于重心的单个子树的结点个数一定小于整个树结点树的一半,所以每次使用重心分割,都能将这个树规模至少降低一半。
实现
重心
siz[i]
表示结点i为根的子树的结点数;
val[i]
表示这个结点的儿子为根的子树的结点数最大值(重心即为val值最小的结点);
int val[MAXN],siz[MAXN],stk[MAXN],top;
void calc_g(int u,int pa)//计算val值和siz值
{
stk[++top]=u;//将处理过的结点放入栈
siz[u]=1;val[u]=0;
for(Edge *p=V[u];p;p=p->nxt)
{
int v=p->v;
if(v!=pa&&)
{
calc_g(v,u);
siz[u]+=siz[v];
val[u]=max(val[u],siz[v]);
}
}
}
int get_center(int u)//获取重心
{
top=0;
calc_g(u,0);
int res,best=0x7FFFFFFF;
for(int i=1;i<=top;i++)
{
int v=stk[i];
val[v]=max(val[v],siz[u]-siz[v]);//结点v连父亲的边的结点数也要考虑
if(best>val[v])
{best=val[v];res=v;}
}
return res;//重心
}
分治
找一个重心,然后把重心的邻接点放入栈(表示被分出来的其它区块),然后处理每个区块,维护需要的数据结构,用vis标记此时的重心,这样下次就不会访问这个重心,从而下次的区块就被限制了,无法通过重心到其它区块。
一个例子:
//SPOJ_FTOUR2的部分代码
int solve()
{
head=tail=1;
que[1]=1;
int res=0;
while(head<=tail)
{
int u=que[head++];
int g=find_center(u);//找到此区块的重心
BIT::insert(crow[g],0,id);//本题维护了一个树状数组,O(logn)
vis[g]=true;//标记重心
for(Edge *p=V[g];p;p=p->nxt)
{
int v=p->v;
if(!vis[v])
{
top=0;
res=max(res,calc(v,g,crow[v],p->val));//遍历区块,维护数据结构
for(int i=1;i<=top;i++)
BIT::insert(stk[i].first+crow[g],stk[i].second,id);//继续维护数据结构
que[++tail]=v;//将新的区块入队
}
}
}
return res;
}
例题一道
POJ2114
题意
给一棵树,边带权,求这棵树中是否有长度恰好为K的路径。
树的点分治
u为找到的重心,然后访问每个区块,把与u的距离放进集合S。
一边放进集合S,一边检查集合中是否已经存在路径,使得与当前距离相加刚好等于K
如果没有,继续分治:
OK!