• 分治思想及树上点分治


    分治思想在OI中是一种常见的思想。分治的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。即一种分目标完成程序算法,简单问题可用二分法完成。

    当我们求解某些问题时,由于这些问题要处理的数据相当多,或求解过程相当复杂,使得直接求解法在时间上相当长,或者根本无法直接求出。对于这类问题,我们往往先把它分解成几个子问题,找到求出这几个子问题的解法后,再找到合适的方法,把它们组合成求整个问题的解法。如果这些子问题还较大,难以解决,可以再把它们分成几个更小的子问题,以此类推,直至可以直接求出解为止。这就是分治策略的基本思想。

    总结来说,分治就是“分而治之”,可以把一个复杂的问题简单化,从全局变成局部,逐渐缩小问题的规模,更加高效的解决问题。

    在我们计算机入门的过程中,其实分治思想已经接触得不少了。最基础又经典的例子便是快速排序,归并排序和快速幂。

    不妨先来复习一下这三种“基本功”:

    快速排序:

    void qsort(int l,int r)
    {
        if(r-l<=1)
        return;
        int x=l+rand()%(r-l);
        swap(a[r-1],a[x]);
        x=a[r-1];
        int i;
        int j=l;
        for(int i=l;i<r;i++)
        {
            if(a[i]<=x)
            swap(a[i],a[j++]);
        }
        qsort(l,j-1);
        qsort(j,r);
    }

    归并排序:

    void msort(int l,int r)
    {
        if(r-l<=1)
        return;
        int mid=(l+r)/2;
        msort(l,mid);
        msort(mid,r);
        int i=l;
        int j=mid;
        int k=l;
        while (i<mid&&j<r)
        {
            if(a[i]<a[j])
            b[k++]=a[i++];
            else
            b[k++]=a[j++];
        }
        while(i<mid)
        b[k++]=a[i++];
        while(j<r)
        b[k++]=a[j++];
        memcpy(a+l,b+l,(r-l)*sizeof(int));
    }

    快速幂(非递归):

    int pow(int x,int y)
    {
        if(y==0)
            return 1;
        else
        {
            int t=pow(x,y/2);
            if(y%2==0)
                return t*t;
            else
                return t*t*x;
        }
    }

    快速幂(递归):

    int pow(int x,int y)
    {
        int ans=1;
        while(y)
        {
            if(y&1)
                ans*=x;
            x*=x;
            y>>=1;
        }
        return ans ;
    }

    将这几段代码互相比较,便可以较为容易地加深对分治思想的理解。以后在一些高端的算法中也会经常用到分治思想。

    例如:

    矩阵乘法快速幂加速递推
    CDQ分治
    整体二分
    快速傅里叶变换
    快速沃尔什变换
    莫比乌斯变换
    辛普森积分等等。
     
    以上的一些问题大多数都属于序列分治的范畴内。了解了分治思想之后,将其与树上问题结合起来,便是我们的树上分治了。由于树上的点分治在树的分治问题中操作相对简单,用途广泛,本文主要深入研究树上的点分治问题。
    首先,树上点分治是用来干什么的呢?通常用来处理树上路径统计问题。先选取一个部分,统计经过这个部分的全部路径数,再递归地处理该部分的每一个子树,易知,“部分”的选取在树的点分治问题中十分重要。一般我们采取的方式是,对于一个节点,处理出其左子树信息,再处理出右子树信息,然后将两部分的信息结合起来,放在一起统计,从而达到分治处理树的统计问题的目的。那么我们上文也已经提到,“部分”,即分治中心不能随意选取,一般我们选取重心,原因与时间复杂度有关,论证较为复杂,在此不多赘述。重心的求法在以前树上问题的学习中应该已经明确,这里给出代码:
    void getroot(int x,int fa)
    {
        mx[x]=0;
        si[x]=1;
        for(int i=head[x];i;i=next[i])
        {
            if(to[i]!=fa&&!vis[to[i]])
            {
                getroot(to[i],x);
                si[x]+=si[to[i]];
                mx[x]=max(mx[x],si[to[i]]);
            }
        }
        mx[x]=max(mx[x],sn-si[x]);
        if(mx[root]>mx[x])
            root=x;
    }

    求出重心之后,利用树中所有点到重心的距离,统计满足条件的路径,然后递归子树,继续进行分治过程。在统计的过程中,一般情况下我们可以通过单步容斥原理,即用“从子树中任意选出两个点”的结果减去“从同一个儿子的子树中任意选出两个点”的结果,得到每次分治的结果。对于 x 的每一个儿子,用同样的方法便可得到正确统计结果。注意,在用单步容斥原理时需要满足一个条件,即统计结果可以逆运算,如果不存在逆运算,最典型的例子便是统计最大值和最小值,像这样的问题只能就题论题,通过特殊方法加以解决。

    两道模板题:POJ 1741 BZOJ 2152

    最裸的点分治练习题,初学者可以尝试一下。其中BZOJ 2152题解戳http://www.cnblogs.com/kanbokedeshiwoerzi/p/8809919.html

  • 相关阅读:
    Java 入门 9 Random 类 随机数
    Java 入门16 面向对象实现之前的小案例购物车
    Java 入门10 java的内存分配,数组内存图( 方法区 栈内存 堆内存)
    JAVA 12 联系回顾之前知识
    Java 13 面向对象的编程 小案例 实现购物车
    面试泛型
    面试反射
    从HashMap的执行流程开始 揭开HashMap底层实现
    面试什么是面向对象?面向对象三大特性?
    面试JMM内存模型
  • 原文地址:https://www.cnblogs.com/kanbokedeshiwoerzi/p/8810112.html
Copyright © 2020-2023  润新知