• 点分治总结


      这几天学了学点分治

      点分治是一种处理树上路径/点对的计数问题的高效算法,它的递归层数在logn之内

      我们考虑如何对一棵树进行分治:因为要让递归层数尽可能的小,我们要选一个点,使得这个点的最大子树大小最小,即这棵树的重心,然后对路径进行处理,删去该点之后,关于剩下的几棵树进行分治

      显然,这样的算法,递归层数最大是logn的

      然后我们考虑如何对路径进行处理:因为不包含当前点的路径一定会在其它点的分治中处理到,因此我们只需要处理包含当前点的路径即可。既然这样,我们就可以像维护子树信息一样地维护通过当前点的路径了。我们维护f和g两个数组,分别表示当前子树的信息和已计算过的子树信息,我们只需要将f数组和g数组做相关的运算,最后将f数组加入到g数组中即可。全部处理完之后要记得将g数组清零

      这样处理我们得到的是满足答案的所有无序的二元组,如果题目要求所有有序的二元组,将答案*2即可

      代码大概是这样的:

    void getroot(int x, int par) {//经典树形DP,找到当前树的重心
        size[x] = 1; maxsize[x] = 0;
        for (int i = lin[x], y; i; i = edge[i].ne) {
            if (vis[y = edge[i].y] || y == par) continue;
            getroot(y, x);
            size[x] += size[y];
            maxsize[x] = std::max(maxsize[x], size[y]);
        }
        maxsize[x] = std::max(maxsize[x], sum - size[x]);
        if (maxsize[x] < maxsize[root]) root = x;
    }
    
    void cal(int x, int par) {//计算当前子树对答案的贡献
        //处理当前节点和已计算的节点之间产生的贡献
        for (int i = lin[x], y; i; i = edge[i].ne) {
            if (vis[y = edge[i].y] || y == par) continue;
            //在递归到y之前处理y的相关信息
            cal(y, x);
        }
    }
    
    void add(int x, int par, bool flag) {
        //若flag为false,则将当前节点信息加入到已计算的节点的信息中,否则将已计算的节点的信息清零
        for (int i = lin[x], y; i; i = edge[i].ne) {
            if (vis[y = edge[i].y] || y == par) continue;
            add(y, x, flag);
        }
    }
    
    void solve(int x) {
        getroot(x);//找到重心之后,关于重心再做一遍DFS,得到所有点的size
        vis[x] = true;//删去当前点
        //将当前加入到已计算的节点的信息中
        for (int i = lin[x], y; i; i = edge[i].ne) {
            if (vis[y = edge[i].y]) continue;
            cal(y, x);
            add(y, x, false);
        }
        for (int i = lin[x], y; i; i = edge[i].ne) {
            if (vis[y = edge[i].y]) continue;
            add(y, x, true);
        }
        for (int i = lin[x], y; i; i = edge[i].ne) {
            if (vis[y = edge[i].y]) continue;
            sum = size[y];
            root = 0;
            getroot(y, 0);
            solve(root);
        }
    }

      有几个需要注意的点:

        因为每次root都要重置为0,因此我们要让maxsize0是一个最大值。

        第一次solve前需要让sum = n

        注意检查分治时调用的变量是不是solve(root)

        一定要先处理重心并删去重心

        solve内不再getroot一次的话,与x相邻的点中有一个点的size是不正确的,这可能会导致复杂度的不正确

  • 相关阅读:
    前端总结--性能优化
    Vue面试中,经常会被问到的面试题/Vue知识点整理
    面试怎么样?才会容易进入到心仪公司了
    Vuex,从入门到入门
    当面试官问你“有什么缺点”时,应如何体面的回答?
    Linux下文件搜索、查找、查看命令
    线程池运行机制
    win10右键很慢
    Linux 安装 Tomcat7
    Tomcat / Nginx 跨域
  • 原文地址:https://www.cnblogs.com/hinanawitenshi/p/8689949.html
Copyright © 2020-2023  润新知