不同之处:
边分治对菊花图的情况复杂度会退化,所以需要重新建图(添加虚点),使其变成二叉树。!!注意:添加的虚点不能影响原答案,假如只考虑距离,那么下图中的 1-6, 6-3, 6-7, 7-4, 7-5 的边权应当为 0
重建完成后,最多 4 * n 个结点
重建树的代码
void rebuild(int p,int fa){
int last = 0;
for(int i = Inihead[p]; ~i;i = Iniedge[i].nex){ ///Iniedge 为原图
int v = Iniedge[i].v,w = Iniedge[i].w;
if(v == fa) continue;
if(!last) {
reAddEdge(p,v,w); ///reAddEdge(u,v,w),直接建立正反向边
last = p;
}
else {
int k = ++ cpyn; ///初始时 cpyn == n
reAddEdge(last,k,0);
reAddEdge(k,v,w);
last = k;
}
rebuild(v,p);
}
}
相同之处:
每一次都找树重心(中心边),将所有的路径都分成是否经过树重心(中心边),假设经过为情况1,未经过为
情况2,那么树分治也就是处理完情况1后,递归分治 (情况2)
对情况1:
需要做的就是处理所有点到树重心(或者中心边的 u,v) 如距离(复杂度 为O(当前连通块大小))等的信息。。。。最好还是写几个题,能够理解更深
寻找重心代码:
/// 调用时 sum 的值为 n (n 为结点总数),mi 为 inf,最后 rt 的得到的重心
void getroot(int &mi, int &rt, int p, int fa, int sum) {
int maxx = 0;
sonnum[p] = 1;
for (int i = head[p]; ~i; i = edge[i].nex) {
int v = edge[i].v;
if (v == fa || vis[v]) continue;
getroot(mi, rt, v, p, sum);
sonnum[p] += sonnum[v];
maxx = max(maxx,sonnum[v]);
}
if (mi > max(maxx, sum - sonnum[p])) {
mi = max(maxx, sum - sonnum[p]);
rt = p;
}
}
寻找中心边代码:
/// 调用时 sum 的值为 cpyn(cpyn 为重建树后的结点总数),minn 为 inf,最后 rt 的得到的中心边
void getroot(int &minn,int &rt,int p,int fa,int sum){
sonnum[p] = 1;
for(int i = head[p]; ~i;i = edge[i].nex){
int v = edge[i].v;
if(v == fa || vis[i >> 1]) continue; ///存边的时候. 0、1 为一组,1、2为一组。故标记时标记i>>1即可
getroot(minn,rt,v,p,sum);
sonnum[p] += sonnum[v];
if(minn > max(sonnum[v],sum - sonnum[v])){
minn = max(sonnum[v],sum - sonnum[v]);
rt = i;
}
}
}
关于复杂度:
仅仅计算分治的复杂度:
每个结点最多计算 l o g n logn logn 次,故复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
推荐题目:
POJ 1741 (点分治模版),题解:POJ 1741
HDU 4812(点分治, 技巧去同一子树贡献),题解:HDU 4812
HDU 4670(点分治 + 状压),题解:HDU 4670
SPOJ QTREE4 Query on a tree IV (边分治 + 用堆维护分治结构) ,题解:SPOJ QTREE4