zoukankan      html  css  js  c++  java
  • 点分治

    1 概述

    点分治是一种对树上符合某种条件的路径进行静态统计的算法。

    当然,动态点分治(点分治树)是有的,但此篇文章暂不涉及。

    这种算法的思想就是:对于一棵树,可以把树上的路径分为两类,一类是经过根结点的,一类是不经过根结点的。对于第二类路径,我们可以通过将根节点的每棵子树作为子问题递归处理,这样,我们只需要考虑如何处理第一种路径即可,使问题简化。

    2 例题

    2.0 题目链接

    Poj1741 Tree

    2.1 题目大意

    给出一棵有N个点的树,树上的每条边都有一个权值,求长度不超过K的路径有多少条。

    2.2 思路

    经典的点分治问题。

    我们先选一个结点作为根,然后开始处理上文中提到的“第一类路径”,即经过根节点的路径,其余路径递归处理。

    设结点x到根的距离为dis[x],我们先对当前这棵树dfs一遍,将遍历到的结点都存在一个数组que中,然后按dis的值从小到大排序。接着,我们定义两个指针i和j。其中i从左到右扫描这个数组,j从右到左扫描这个数组。对于每一个i,我们都找出一个最靠右的j使得dis[que[i]]+dis[que[j]]≤K,此时可将j-i累加到答案中,直到i≥j为止。可以发现,j的位置只会越来越靠左,故这个统计答案过程可以在O(Nlog2N+N)的时间内实现。

    值得注意的是,我们在选择让哪个点作为根时一定要选树的重心。这样的话,在每一次的递归分治中,结点的规模至少减半,可以保证最多只会进行log2N层点分治。倘若不选树的重心作为根,则点分治进行的层数无法保证,时间复杂度也极易退化。

    每次选择树的重心作为根后,总时间复杂度即为O(Nlog22N)。

    2.3 代码实现

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
        using namespace std;
    struct edge
    {
        int last;
        int end;
        int weight;
    }e[20005];
        //vis记录每个点是否被访问过 
        //que为存放结点的数组
        //dis为每个结点到根的距离
        //siz为每个结点的子树大小
        //msiz为每个结点的所有子树中,最大的子树的大小 
        //siz和msiz都是用来求树的重心的
        int n=0,k=0,ne=0,head=0,tail=0,note[10005],vis[10005],que[10005],dis[10005],siz[10005],msiz[10005];
    void csh()//多组数据,记得初始化 
    {
        ne=head=tail=0;
        for(int i=0;i<20005;i++) e[i].last=e[i].end=e[i].weight=0;
        for(int i=0;i<10005;i++) note[i]=vis[i]=que[i]=dis[i]=siz[i]=msiz[i]=0;
    }
    void make_edge(int u,int v,int w)
    {
        ne++;
        e[ne].last=note[u];
        e[ne].end=v;
        e[ne].weight=w;
        note[u]=ne;
    }
    bool cmp(int x,int y)
    {
        return dis[x]<dis[y];
    }
    void dfs(int x)
    {
        siz[x]=vis[x]=1,msiz[x]=0;
        que[++tail]=x;//存储遍历到的结点 
        for(int i=note[x];i;i=e[i].last)
            if(!vis[e[i].end])
            {
                dis[e[i].end]=dis[x]+e[i].weight;
                dfs(e[i].end);
                siz[x]+=siz[e[i].end];
                msiz[x]=max(msiz[x],siz[e[i].end]);
            }
        vis[x]=0;
    }
    int calc(int l,int r) 
    { 
        sort(que+l,que+r+1,cmp);//先按dis的值从小到大排序 
        int ans=0;
        for(int i=l,j=r;i<j;i++)//两个指针一起扫 
        {
            while(i<j&&dis[que[i]]+dis[que[j]]>k) j--;
            ans+=j-i;//累加答案 
        }
        return ans;
    }
    int work(int x)
    {
        head=1,tail=0;
        dfs(x);//先dfs一遍(用于找树的重心) 
        int sum=siz[x],misiz=msiz[x];
        for(int i=1;i<=tail;i++)//找树的重心 
            if(max(msiz[que[i]],sum-msiz[que[i]])<misiz) misiz=max(msiz[que[i]],sum-msiz[que[i]]),x=que[i];
        vis[x]=1;//标记这个点已被计算过,防止程序对重复的点进行点分治 
        int ans=0;
        head=1,tail=0;
        for(int i=note[x];i;i=e[i].last)
            if(!vis[e[i].end])
            {
                dis[e[i].end]=e[i].weight;
                dfs(e[i].end);//收集结点 
                ans-=calc(head,tail);//先减去路径两端在同一棵子树的不合法路径对答案的贡献 
                head=tail+1;
            }
        dis[x]=0,que[++tail]=x;
        ans+=calc(1,tail);//计算路径两端在不同子树的路径对答案的贡献 
        for(int i=note[x];i;i=e[i].last)
            if(!vis[e[i].end]) ans+=work(e[i].end);//递归分治 
        return ans;
    }
    int main()
    {
        for(;;)
        {
            scanf("%d%d",&n,&k);
            if(n==0&&k==0) break;
            csh();
            for(int i=1;i<=n-1;i++)
            {
                int u=0,v=0,w=0;
                scanf("%d%d%d",&u,&v,&w);
                make_edge(u,v,w);
                make_edge(v,u,w);
            }
            printf("%d
    ",work(1));
        }
        return 0;
    }

    2.4 小结

    每道题计算路径对答案的贡献的方式都不一样,但点分治的总体框架与思想是不变的。

    分治这种算法还是要多做题,多总结才行。

    3 练习题

    Luogu P2634 [国家集训队]聪聪可可

    Luogu P4149 [IOI2011]Race

    4 参考资料

    《算法竞赛进阶指南》(李煜东著)第0x45节。

    LZZ的言传身教。

  • 相关阅读:
    正则表达式
    悟透JavaScript
    2时序逻辑电路寄存器
    MSP430F5438时钟系统
    2时序逻辑电路移位寄存器
    1组合逻辑电路基本门电路
    1组合逻辑电路多路选择器与多路分解器
    2时序逻辑电路触发器与锁存器
    1组合逻辑电路编码器和译码器
    ASCII与汉字编码方法
  • 原文地址:https://www.cnblogs.com/wozaixuexi/p/9464950.html
Copyright © 2011-2022 走看看