• POJ1741:Tree——题解+树分治简要讲解


    http://poj.org/problem?id=1741

    题目大意:给一棵树,求点对间距离<=k的个数。

    ————————————————————

    以这道题为例记录一下对于树分治的理解。

    树分治分为两类,一类是基于点的分治,一类是基于边的分治。

    后者与树链剖分很相似,但是一般用不上,这里讲的是前者。

    我们一般进行树分治找的点都是这棵树的重心(即子树最大者最小的点),我们每次操作都做与这个点相关的路径,然后删除这个点再重新寻找。

    分重心的好处在于我们近似的将树分成了两份,类似于二分,其深度不超过O(logn)(其实有严格证明的,但是我太弱了,不会写)

    分完重心的操作大致三种

    1.找u,v,其中u,v在重心s的同一棵子树上(这种情况直接忽略,因为看下面的操作我们就能明白我们可以递归的完成这个操作)

    2.找u,v,其中u,v在重心s的两棵子树上。

    3.找u,查找u到重心s的路径。

    我们发现3操作和2操作很相似,我们直接讨论2操作。

    显然我们在2操作的路径当中不可避免的要经过s,所以我们从s开始bfs,求出每个点i到s的距离dis[i],我们的路径长度即为dis[u]+dis[v]。

    3操作同理只是变成了dis[u]+dis[s],其中dis[s]=0.

    这里提供一种简要算法:我们在求完dis之后对我们求的dis排序,这样我们就可以快速的求出点对距离<=k的个数。

    但是这样就不可避免的要判重,为什么呢?

    废话你这样排不就有可能把1操作的一部分点对先算了一遍,这样明显会导致答案变大。

    那怎么办呢?我们对于每一棵子树,再删掉我们通过2操作得到的点对即可。

    (现将s删掉,s的儿子dis[u]不变的情况下以u为起点bfs求点对,则这些点对就是在同一棵子树当中被计算的重复的点对,减去即可。)

    #include<cmath>
    #include<cstdio>
    #include<queue>
    #include<cctype>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int N=10001;
    inline int read(){
        int X=0,w=0; char ch=0;
        while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
        while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
        return w?-X:X;
    }
    struct node{
        int w;
        int to;
        int nxt;
    }edge[N*2];
    int cnt,n,k,head[N],q[N],dis[N],size[N],son[N],d[N],fa[N];
    ll ans;
    bool vis[N];
    void add(int u,int v,int w){
        cnt++;
        edge[cnt].to=v;
        edge[cnt].w=w;
        edge[cnt].nxt=head[u];
        head[u]=cnt;
        return;
    }
    int calcg(int st){
        int r=0,g,maxn=n;
        q[++r]=st;
        fa[st]=0;
        for(int l=1;l<=r;l++){
        int u=q[l];
        size[u]=1;
        son[u]=0;
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].to;
            if(vis[v]||v==fa[u])continue;
            fa[v]=u;
            q[++r]=v;
        }
        }
        for(int l=r;l>=1;l--){
        int u=q[l],v=fa[u];
        if(r-size[u]>son[u])son[u]=r-size[u];
        if(son[u]<maxn)g=u,maxn=son[u];
        if(!v)break;
        size[v]+=size[u];
        if(size[u]>son[v])son[v]=size[u];
        }
        return g;
    }
    inline ll calc(int st,int L){
        int r=0,num=0;
        q[++r]=st;
        dis[st]=L;
        fa[st]=0;
        for(int l=1;l<=r;l++){
        int u=q[l];
        d[++num]=dis[u];
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].to;
            int w=edge[i].w;
            if(vis[v]||v==fa[u])continue;
            fa[v]=u;
            dis[v]=dis[u]+w;
            q[++r]=v;
        }
        }
        ll ecnt=0;
        sort(d+1,d+num+1);
        int l1=1,r1=num;
        while(l1<r1){
        if(d[l1]+d[r1]<=k){
            ecnt+=r1-l1;
            l1++;
        }else r1--;
        }
        return ecnt;
    }
    void solve(int u){
        int g=calcg(u);
        vis[g]=1;
        ans+=calc(g,0);
        for(int i=head[g];i;i=edge[i].nxt){
        int v=edge[i].to;
        int w=edge[i].w;
        if(!vis[v])ans-=calc(v,w);
        }
        for(int i=head[g];i;i=edge[i].nxt){
        int v=edge[i].to;
        if(!vis[v])solve(v);
        }
        return;
    }
    int main(){
        while(scanf("%d%d",&n,&k)!=EOF&&n+k){
        cnt=ans=0;
        memset(head,0,sizeof(head));
        memset(vis,0,sizeof(vis));
        for(int i=1;i<n;i++){
            int u=read();
            int v=read();
            int w=read();
            add(u,v,w);
            add(v,u,w);
        }
        solve(1);
        printf("%lld
    ",ans);
        }
        return 0;
    }
  • 相关阅读:
    leetcode ——187. 重复的DNA序列
    leetcode——30. 串联所有单词的子串
    leetcode——520. 检测大写字母
    leetcode——686. 重复叠加字符串匹配
    leetcode——459. 重复的子字符串
    leetcode——28. 实现 strStr()
    leetcode——17. 电话号码的字母组合
    leetcode——14. 最长公共前缀
    NAT模式实现局域网物理机与虚拟机的互通访问
    .Net Core项目发布到虚拟机(三)
  • 原文地址:https://www.cnblogs.com/luyouqi233/p/8035910.html
Copyright © 2020-2023  润新知