zoukankan      html  css  js  c++  java
  • [洛谷P4178] Tree (点分治模板)

    题目略了吧,就是一棵树上有多少个点对之间的距离 (leq k)
    (n leq 40000)


    算法##

    首先有一个 (O(n^2)) 的做法,枚举每一个点为起点,(dfs) 一遍可知其它点到这个点的距离,统计一下即可。

    但是这样太慢了。于是考虑“分治”这种神奇的做法。

    第一步,选一个点做根节点,这个点便是树的重心(代码中找重心 (getroot) ),它满足每棵子树节点数不超过 (n/2) ,即可保证子问题规模减半。

    第二步,寻找所有经过根节点的路径,这些路径可以写成 (u leadsto rt + v leadsto rt)
    但是这样有一个问题,(u)(v)(lca) 可能不是 (rt) ,即 (u leadsto v) 不经过 (rt)
    于是我们用容斥原理,枚举 (rt) 的每一个儿子把这种情况减去(代码 (work) 中的 $ ans-=cal(v,p o len)$)

    顺便提一句如何计算有多少小于 (k) 的路径,两个指针扫来扫去就行了(代码中的 (cal) )
    复杂度 (O(n))

    第三步,对于 (rt) 的每一棵子树进行递归(第一步)。


    分析##

    个人认为算法的重点有两个:

    1. (cal) 复杂度可为 (O(n))
      2.重心可使每棵子树规模减半
      这样就使得整个算法复杂度为 (O(nlogn))

    代码##

    终于会点分治了,很开心啊~

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    
    using namespace std;
    
    const int N = 40005;
    
    struct node{
        node *next;
        int v,len;
    }pool[N*2],*h[N];
    int cnt;
    void addedge(int u,int v,int len){
        node *p=&pool[++cnt],*q=&pool[++cnt];
        p->v=v;p->next=h[u];h[u]=p;p->len=len;
        q->v=u;q->next=h[v];h[v]=q;q->len=len;
    }
    
    int sum,rt,n,k;
    int size[N],mx[N],vis[N];
    void getroot(int u,int f){
        int v;
        size[u]=1; mx[u]=0;
        for(node *p=h[u];p;p=p->next){
            if(vis[v=p->v] || v==f) continue;
            getroot(v,u);
            size[u]+=size[v]; mx[u]=max(mx[u],size[v]);
        }
        mx[u]=max(mx[u],sum-size[u]);
        if(mx[u]<mx[rt]) rt=u;
    }
    
    int dep[N],dd;
    void getdeep(int u,int f,int c){
        int v;
        dep[++dd]=c;
        size[u]=1;
        for(node *p=h[u];p;p=p->next){
            if(vis[v=p->v] || v==f) continue;
            getdeep(v,u,c+p->len);
            size[u]+=size[v];
        }
    }
    int cal(int u,int c){
        int t=0;
        dd=0;
        getdeep(u,0,c);
        sort(dep+1,dep+1+dd);
        for(int l=1,r=dd;l<r;l++){
            while(l<r && dep[l]+dep[r]>k) r--;
            t+=r-l;
        } 
        return t;
    }
    
    int ans;
    void work(int u){
        int v;
        vis[u]=1;
        ans+=cal(u,0);
        for(node *p=h[u];p;p=p->next){
            if(vis[v=p->v]) continue;
            ans-=cal(v,p->len);
            
            rt=0; sum=size[v]; getroot(v,u); 
            work(rt);
        }
    }
    
    int main()
    {
        int u,v,l;
        scanf("%d",&n);
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&l);
            addedge(u,v,l);
        }
        scanf("%d",&k);
        
        mx[0]=400005; 
        rt=0; sum=n; getroot(1,0);
        work(rt);
        printf("%d
    ",ans);
        
        return 0;
    }
    
    既然选择了远方,便只顾风雨兼程
  • 相关阅读:
    使用循环计算斐波那契数列
    java 面向过程实现万年历
    学习js回调函数
    序列化和反序列化
    SQL语句增加字段、修改字段、修改类型、修改默认值
    使用SQL语句查询日期(当月天数,当月第一天,当月最后一天,本年最后一天,当月第一个星期) 日期转字符串
    RGB颜色表
    String 与StringBuilder有什么区别
    C# byte数组与Image的相互转换
    CentOS 7 MySQL、Tomcat、Zookeeper设置开机自启
  • 原文地址:https://www.cnblogs.com/lindalee/p/9065001.html
Copyright © 2011-2022 走看看