• CodeChef TRIPS-Children Trips 树上分块


    参考文献国家集训队2015论文《浅谈分块在一类在线问题的应用》-邹逍遥

    题目链接

    题目大意

    一棵n个节点的树,树的每条边长度为1或2,每次询问x,y,z。
    要求输出从x开始走,每次只能走到当前节点距离$le z$的点,问最少几次能走到y

    大致思路

    考虑将树进行深度分块,设$size=sqrt{n}$,对于每个节点x,如果$depth[x]\%size==1$则称它是关键点。
    于是这棵树就被这些关键点分成了若干块(关键点属于它下面的块),如果某一块的大小小于size,就把它和上一个块合并。

    这样这棵树的每个块大小就$ge size$,块的个数就$le size$,并且每个块的直径$le size*4$,可以在$sqrt{n}$的时间求出每个询问

    具体实现

    对于每个节点,预处理它到上面的块中离自己最近的节点(一定是他的祖先)的距离和在z($zle size*2$)
    (当$z>size$时可以一步跨过一个块)的情况下要走多少步,最后一步还剩下多长走z后到达的节点
    以及每个节点的父亲,和到父亲节点的距离(为在块中暴力准备)

    对于每个询问,如果当前两个点不在同一个块,则所在块靠下的点移动到上面的块中离自己最近的节点
    如果在同一个块,则暴力让深度深的点移到它的父亲

    总复杂度$O(nsqrt{n})$

    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    #define maxn 100005
    #define maxs 350
    struct Edge{
        int next,to,w;
    }edge[maxn*2];
    int n,size,fi[maxn],se,stack[maxn],top,depth[maxn],fa[maxn],block[maxn],s,remain[maxn][maxs*2],step[maxn][maxs*2],key[maxs],dis[maxn];
    int reach[maxn][maxs*2],dis1[maxn];
    inline void add_edge(int u,int v,int w){
        edge[++se].next=fi[u],fi[u]=se,edge[se].to=v,edge[se].w=w,
        edge[++se].next=fi[v],fi[v]=se,edge[se].to=u,edge[se].w=w;
    }
    int dfs(int x){//第一次dfs分块,预处理出fa(父亲),dis(到父亲的距离)
        int si=1,k;
        stack[top++]=x;
        for(int i=fi[x];i;i=edge[i].next){
            int v=edge[i].to;
            if(v==fa[x])continue;
            dis[v]=edge[i].w,fa[v]=x,depth[v]=depth[x]+1,si+=dfs(v);
        }
        if(depth[x]%size==0&&si>=size){
            k=++s;block[x]=k;key[k]=x;
            while(stack[--top]!=x){
                block[stack[top]]=k;
            }
        }
        return si;
    }
    void dfs1(int x){//第二次dfs预处理reach[x][z](走z距离能到达的点),dis1(到最近的不在一个块的祖先有多少距离)step(要走几步),remain(还剩多少距离)
        reach[x][0]=x;
        reach[x][1]=dis[x]==1?fa[x]:x;
        for(int i=2;i<=size*2;i++){
            if(i-dis[x]>=1)reach[x][i]=reach[fa[x]][i-dis[x]];
            else reach[x][i]=fa[x];
        }
        if(block[x]==block[fa[x]]){
            int v;
            for(int i=2;i<=size*2;i++){
                if(block[x]==block[v=reach[x][i]]){
                    step[x][i]=step[v][i]+1;
                    remain[x][i]=remain[v][i];
                }
                else{
                    step[x][i]=1;remain[x][i]=remain[fa[x]][i]-dis[x];
                }
            }
            dis1[x]=dis1[fa[x]]+dis[x];
        }
        else{
            for(int i=2;i<=size*2;i++){
                step[x][i]=1;remain[x][i]=i-dis[x];
            }
            dis1[x]=dis[x];
        }
        for(int i=fi[x];i;i=edge[i].next){
            int v=edge[i].to;
            if(v!=fa[x])dfs1(v);
        }
    }
    int query(int l,int r,int p){//对于p<=size*2的询问 
        int ans=0,sup1=0,sup2=0;
        while(block[l]!=block[r]){//如果两个点不在一个块 
            if(block[l]<block[r]){
                if(dis1[l]>sup1)l=reach[l][sup1],ans+=step[l][p],sup1=remain[l][p],l=fa[key[block[l]]];
                else sup1=remain[l][sup1],l=fa[key[block[l]]];
            }
            else{
                if(dis1[r]>sup2)r=reach[r][sup2],ans+=step[r][p],sup2=remain[r][p],r=fa[key[block[r]]];
                else sup2=remain[r][sup2],r=fa[key[block[r]]];
            }
        }
        while(l!=r){//在一个块后就暴力走 
            if(depth[l]>depth[r]){
                if(sup1>=dis[l])sup1-=dis[l],l=fa[l];
                else sup1=p-dis[l],ans++,l=fa[l];
            }
            else {
                if(sup2>=dis[r])sup2-=dis[r],r=fa[r];
                else sup2=p-dis[r],ans++,r=fa[r];
            }
        }
        if(sup1+sup2>=p)ans--;
        return ans;
    }
    int query1(int l,int r,int p){//处理p>size*2的询问 
        int ans=0,sup1=0,sup2=0;
        while(block[l]!=block[r]){
            if(block[l]<block[r]){
                if(dis1[l]<=sup1)sup1-=dis1[l],l=fa[key[block[l]]];
                else{
                    if(sup1>(size<<1)){
                        if(reach[l][size<<1]==reach[l][(size<<1)-1])sup1-=(size<<1)-1;
                        else sup1-=(size<<1);
                        l=reach[l][size<<1];
                    }
                    else{
                        l=reach[l][sup1],ans++,sup1=p;
                    }
                }
            }
            else{
                if(dis1[r]<=sup2)sup2-=dis1[r],r=fa[key[block[r]]];
                else{
                    if(sup2>(size<<1)){
                        if(reach[r][size<<1]==reach[r][(size<<1)-1])sup2-=(size<<1)-1;
                        else sup2-=(size<<1);
                        r=reach[r][size<<1];
                    }
                    else{
                        r=reach[r][sup2],ans++,sup2=p;
                    }
                }
            }
        }
        while(l!=r){
            if(depth[l]>depth[r]){
                if(sup1>=dis[l])sup1-=dis[l],l=fa[l];
                else sup1=p-dis[l],ans++,l=fa[l];
            }
            else {
                if(sup2>=dis[r])sup2-=dis[r],r=fa[r];
                else sup2=p-dis[r],ans++,r=fa[r];
            }
        }
        if(sup1+sup2>=p)ans--;
        return ans;
    }
    int main(){
        int u,v,w,m;
        scanf("%d",&n);size=sqrt(n);
        for(int i=1;i<n;i++)scanf("%d%d%d",&u,&v,&w),add_edge(u,v,w);
        dfs(1);dfs1(1);
        scanf("%d",&m);
        for(int i=0;i<m;i++){
            scanf("%d%d%d",&u,&v,&w);
            if(w<=size*2)printf("%d
    ",query(u,v,w));
            else printf("%d
    ",query1(u,v,w));
        }
        return 0;
    }
  • 相关阅读:
    UpdateBatch到底是怎么用的?
    进度条在.net导入Excel时的应用实例
    asp.net页面触发事件panel滚动条高度不变的实现方法
    .NET中的枚举用法浅析
    .NET程序调试技巧(一):快速定位异常的一些方法
    ASP.NET实现推送文件到浏览器的方法
    微软官方SqlHelper类 数据库辅助操作类
    Asp.net中使用文本框的值动态生成控件的方法
    ASP.NET中Dictionary基本用法实例分析
    ASP.NET动态增加HTML元素的方法实例小结
  • 原文地址:https://www.cnblogs.com/bennettz/p/8533620.html
Copyright © 2020-2023  润新知