化实为虚——点分树 (动态点分治)

有时做题会想到一些与树深有关的做法,随机数据下是可以过的,但深度稍大就无法过。树深做法的问题在于与深度线性相关。给定的一棵树,其深度是不定的。但使用点分树,可以把原树对应到一颗深度为严格 l o g n 的树上。
建立点分树时,每次选取当前块中的重心,我们把子块的重心作为自己重心的儿子,就形成一颗点分树。可以证明其深度不超过 l o g n
这样,一些严重依赖树形态的算法,如树形DP,或从全局角度统计树中的某些量的问题,都可以轻易用点分树动态维护
构建点分树如下。注意到每次重新getgra是在一个子树内完成的,因此不用考虑影响统计其他子树。

int gra, gras; 
int cn; // 当前块大小 
int siz[maxn], vis[maxn];

void getgra(int u, int pa) { //找当前联通块的重心 
    siz[u] = 1; int maxs = 0;
    for(int i = 0; i < G[u].size(); i++) {
        int v = G[u][i].to;
        if(v == pa || vis[v]) continue;
        getgra(v, u);
        siz[u] += siz[v];
        maxs = max(maxs, siz[v]);
    }
    maxs = max(maxs, cn-siz[u]);
    if(gras == -1 || maxs < gras) {
        gras = maxs;
        gra = u;
    }
}

struct dt_node { // 点分树节点 
    int f;
    //记录你要维护的值 
}dt[maxn];
void build(int u, int pa) {
    dt[u].f = pa;
    vis[u] = 1; // 已选作重心 
    int ccn = cn; // ccn = 自己现在块大小(不是子树的) 
    for(int i = 0; i < G[u].size(); i++) {
        int v = G[u][i].to;
        if(vis[v]) continue;
        gras = -1;
        if(siz[v] > siz[u]) cn = ccn - siz[u]; else cn = siz[v]; // 父亲子树与儿子子树计算cn的方式不同 
        getgra(v, u);
        build(gra, u);
    }
}

int main() {
    //...
    cn = n; gras = -1;
    getgra(0, -1);
    build(gra, -1);
}

点分树的应用很多。维护每个点分树节点也有多种类型,如维护子树和对父亲贡献两个值,或维护一个堆记录子节点到它的距离(bzoj1095)等。总之是统计子树内的信息,然后每次更新或询问时就从操作节点在点分树上往父亲走,其实与树深算法如出一辙,只是这是正解。
点分树也与很多其他树上操作技巧结合。可以参考其他树上操作技巧或树上操作技巧总结。

另:标题中“实”指原树,“虚”指与原树等效的树,如LCT、点分树、dfs序线段树(扩展为路径剖分树/欧拉旅游树)等。

猜你喜欢

转载自blog.csdn.net/myjs999/article/details/80151929