【算法学习】树的重心与点分治

树的重心

树的重心也叫做树的质心。其本质是一个点,删除这个点后,形成的子树中最大的节点数目最小。

解法

一遍dfs即可。dfs的时候记录一下当前节点 u u size[u] s i z e [ u ] ,同时记录他的所有儿子子树中的最大节点数目 mxchild m x c h i l d ,那么删除当前节点 u u 所形成的子树就是 max(mxchild,nsize[u]) m a x ( m x c h i l d , n − s i z e [ u ] ) 。维护这个答案的最小值即可求出树的重心。

性质

  1. 树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么他们的距离和一样。

  2. 把两个树通过一条边相连得到一个新的树,那么新的树的重心在连接原来两个树的重心的路径上 。

  3. 把一个树添加或删除一个叶子,那么它的重心最多只移动一条边的距离 。

点分治

先说一些可以参考的资料。

  1. 国家队论文:分治算法在树的路径问题中的应用

  2. 算法竞赛入门指南P392页,路径统计问题

点分治用来解决树上的路径统计问题。复杂度为 nlogn n l o g n 。分治策略的思想是,因为对于一个确定的点 u u ,树上只有两种路径:要么经过 u u ,要么不经过 u u 。每次选取树的重心,统计经过树的重心的路径,此时计算完成后有不合法的路径计算进去,把这些不合法的路径删去后,再递归处理每一个子树。处理子树的方法是上面的方法一致的,找到子树的重心,然后依次处理。

通过如上的方法,可以处理出树上的所有路径。

【算法学习】树的重心与点分治_第1张图片

有几个问题说明一下

  1. 为什么选取重心来进行分治?因为重心有较好的性质,可以保证分治的复杂度不退化。想想一下,如果我们对一条链进行分治,那么可能会造成 n2 n 2 的复杂度,这样显然是不可以接受的。
  2. 为什么会有重复的路径计算?设 dist[u] d i s t [ u ] 表示节点 u u 到当前分治质心的路径和,设 belong[x] b e l o n g [ x ] 为去除质心后的子树的的归属。例如: dist[1]=0,dist[2]=1,dist[6]=2 d i s t [ 1 ] = 0 , d i s t [ 2 ] = 1 , d i s t [ 6 ] = 2 , 又如 belong[5]=3,belong[6]=2,belong[9]=4 b e l o n g [ 5 ] = 3 , b e l o n g [ 6 ] = 2 , b e l o n g [ 9 ] = 4 。当以1为根的时候,我们希望统计出的是经过以1为根的路径。当我们遍历出所有点到1的距离,然后对于其中的任意两个点 u,v u , v ,计算 dist[u]+dist[v] d i s t [ u ] + d i s t [ v ] 即为他们的路径值。这句话对 belong[u]!=belong[v] b e l o n g [ u ] ! = b e l o n g [ v ] 是没问题的,反之是有问题的。如 dist[6]+dist[7] d i s t [ 6 ] + d i s t [ 7 ] 并不会经过点1。如何删除这些答案呢?那么就是递归到质心的子树中,遍历这些子树,把以子树为根的节点统计出的答案全部删掉即可,这样计算出来的就是经过1的路径条数了。

几个注意点

  1. 注意在找质心的时候,不要遍历已经进行过决策的顶点,所以要设置 visit v i s i t 数组。(本质是在除去质心的子树上找他们的质心)
  2. 找质心的时候,要设置全局的节点数目 subtreesize s u b t r e e s i z e ​ 。他表示的是当前要找子树上的节点数目。
  3. 统计答案的时候,具体问题具体分析。

模板

void getroot(int u, int f) {
    // 找质心
    sz[u] = 1;
    int mxchild = 0;
    for(int i = head[u]; i != -1; i = e[i].nxt ) {
        int v = e[i].to;
        if(v != f && !visit[v]) {
            getroot(v, u);
            sz[u] += sz[v];
            mxchild = max(mxchild, sz[v]);
        }
    }
    int tmp = max(mxchild, subtreesize - sz[u]);
    if(tmp < nowmn) {
        nowmn = tmp;
        rt = u;
    }
}
void getdis(int u, int f, int dis) {
    // 算距离,dis是初始距离
    dist[dis % 3] ++ ;
    for(int i = head[u]; i != -1; i = e[i].nxt) {
        int v = e[i].to;
        if(v != f && !visit[v])
            getdis(v, u, (dis + e[i].w) % 3);
    }
}
ll getans(int u, int initdis) {
    // 统计答案
    dist[0] = dist[1] = dist[2] = 0;
    getdis(u, u, initdis);
    return 2ll * dist[1] * dist[2] + 1ll * dist[0] * dist[0];
}

void solve(int u) {
    ans += getans(u, 0); // 统计进过u的答案
    visit[u] = true;
    for (int i = head[u]; i != -1; i = e[i].nxt) {
        int v = e[i].to;
        if (!visit[v]) {
            ans -= getans(v, e[i].w); // 删除在一颗子树中的答案
            nowmn = INF; rt = 0; subtreesize = sz[v];
            getroot(v, u);
            solve(rt);
        }
    }
}

你可能感兴趣的:(算法学习,算法---点分治)