luogu题解 P4092 [HEOI2016/TJOI2016]树树链剖分

Posted rye-catcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了luogu题解 P4092 [HEOI2016/TJOI2016]树树链剖分相关的知识,希望对你有一定的参考价值。

  • 题目链接:

    https://www.luogu.org/problemnew/show/P4092

  • 瞎扯--(O(Q log^3 N))解法

    这道先yy出了一个(O(Q log^3 N)),的做法,先树链剖分。

    对于加标记操作,找到那个点所在的链,将其(top)标记一下,然后该点到根节点区间和+1.

    对于查询操作,先看这个点所在链有没有标记,如果没有,就一直向上跳直到找到一条标记了的链,然后在那条链上根据到根节点区间和进行倍增/二分

    然后出去吃饭的时候忽然想到了(O(Q log^2 N))的解法,于是刚刚这个解法刚打完还没有查错,放在这做一个参考

    代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cctype>
    #include <algorithm>
    #include <cmath>
    #define ll long long 
    #define ri register int 
    using namespace std;
    const int maxn=100005;
    const int inf=0x7fffffff;
    template <class T>inline void read(T &x){
       x=0;int ne=0;char c;
       while(!isdigit(c=getchar()))ne=c==‘-‘;
       x=c-48;
       while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
       x=ne?-x:x;
       return ;
    }
    int n,q;
    struct Edge{
       int ne,to;
    }edge[maxn<<1];
    int h[maxn],num_edge=0;
    inline void add_edge(int f,int t){
       edge[++num_edge].ne=h[f];
       edge[num_edge].to=t;
       h[f]=num_edge;
       return ;
    }
    int dep[maxn],fa[maxn],size[maxn],son[maxn],top[maxn],dfn[maxn],rnk[maxn],cnt=0;
    void dfs_1(int now){
       int v;size[now]=1;
       for(ri i=h[now];i;i=edge[i].ne){
           v=edge[i].to;
           if(v==fa[now])continue;
           fa[v]=now,dep[v]=dep[now]+1;
           dfs_1(v);
           size[now]+=size[v];
           if(!son[now]||size[son[now]]<size[v])son[now]=v;
       }
       return ;
    }
    void dfs_2(int now,int t){
       int v;top[now]=t;
       dfn[now]=++cnt,rnk[cnt]=now;
       if(!son[now])return ;
       dfs_2(son[now],t);
       for(ri i=h[now];i;i=edge[i].ne){
           v=edge[i].to;
           if(v==fa[now]||v==son[now])continue;
           dfs_2(v,v);
       }
       return ;
    }
    int sum[maxn<<2],tag[maxn<<2],L,R,dta,ok[maxn];
    void build(int now,int l,int r){
       if(l==r){
          sum[now]=ok[rnk[l]];
          return ;
       }
       int mid=(l+r)>>1;
       build(now<<1,l,mid);
       build(now<<1|1,mid+1,r);
       return ;
    }
    void pushdown(int now,int ln,int rn){
      if(tag[now]){
          sum[now<<1]+=tag[now]*ln;
          sum[now<<1|1]+=tag[now]*rn;
          tag[now<<1]+=tag[now];
          tag[now<<1|1]+=tag[now];
          tag[now]=0;
      }
      return ;
    }
    void update(int now,int l,int r){
      if(L<=l&&r<=R){
          sum[now]+=dta*(r-l+1);
          tag[now]+=dta;
          return ;
      }
      int mid=(l+r)>>1;
      pushdown(now,mid-l+1,r-mid);
      if(L<=mid)update(now<<1,l,mid);
      if(mid<R)update(now<<1|1,mid+1,r);
      sum[now]=sum[now<<1]+sum[now<<1|1];
      return ;
    }
    int query(int now,int l,int r){
     if(L<=l&&r<=R){
         return sum[now];
     } 
     int mid=(l+r)>>1,ans=0;
     pushdown(now,mid-l+1,r-mid);
     if(L<=mid)ans+=query(now<<1,l,mid);
     if(mid<R)ans+=query(now<<1|1,mid+1,r);
     sum[now]=sum[now<<1]+sum[now<<1|1];
     return ans;
    }
    void update_path(int x,int y){
      dta=1;ok[top[x]]=1;//该条链上有一个标记的点
      while(top[x]!=top[y]){
         if(dep[top[x]]<dep[top[y]])swap(x,y);
         L=dfn[top[x]],R=dfn[x];
         update(1,1,n);
      }
      if(dfn[x]<dfn[y])swap(x,y);
      L=dfn[x],R=dfn[y];
      update(1,1,n);
      return ;
    }
    inline int solve(int x,int y){
      int tmp,val,p=0,k=1,len,ans=0;
      bool flag=0;
      while(top[x]!=top[y]){
          if(dep[top[x]]<dep[top[y]])swap(x,y);
          len=dfn[x]-dfn[top[x]];
          if(ok[top[x]]){
               L=dfn[top[x]],R=dfn[x],
               tmp=query(1,1,n);
               p=0,k=1,flag=0;
               while(k!=0){
               L=dfn[x+p+k],R=dfn[x];
               if(query(1,1,n)>tmp)flag=1,k=k>>1;
               else p=p+k,k=k<<1;
               while(p+k>len)k=k>>1;
               }
               if(flag)return ans+dfn[x+p]-dfn[x];
          }
          ans+=len;
          x=fa[top[x]];
      }
      if(dfn[x]>dfn[y])swap(x,y);
      L=dfn[x],R=dfn[y],len=dfn[y]-dfn[x];
      tmp=query(1,1,n);
      p=0,k=1;
      //cout<<y<<endl;
      if(x==y)return ans;
      while(k!=0){
          L=dfn[x+p+k],R=dfn[x];
          if(query(1,1,n)>tmp)k=k>>1;
          else p=p+k,k=k<<1;
          //if(y==3)cout<<k<<‘ ‘<<p<<endl;
          while(p+k>len)k=k>>1;
      }
      return ans+dfn[x+p]-dfn[x];
    }
    int main(){
       char opt[5];
       int x,y,z;
       read(n),read(q);
       for(ri i=1;i<n;i++){
          read(x),read(y);
          add_edge(x,y);
          add_edge(y,x);
       }
       dep[1]=1,fa[1]=0;
       dfs_1(1);
       dfs_2(1,1);
       ok[dfn[1]]=1;
       build(1,1,n);
       while(q--){
           scanf("%s",opt);
           if(opt[0]==‘C‘){
               read(x);
               //cout<<x<<"-----"<<endl;
               update_path(1,x);
           }
           else{
               read(x);
               //cout<<x<<"***"<<endl;
               printf("%d
    ",solve(x,1));
           }
       }
       return 0;
    }
    
  • 分析---(O(Q log^2 N))解法

    首先我想到了一个错误的解法,就是因为链是线段树上一个连续的区间,每个([dfn[x],dfn[top[x]]])线段树区间有个(mx)值,表示,(x)(top[x])路径中距离它最近标记的祖先,加标记时比较原有标记深度与新标记深度然后更新。查询的时候查询(x)(top[x])的区间最大之就可以了,如果没有,就一直往上跳直至找到

    然而这个解法有个错误我SB地没有发现,就是你更新区间最大值时,(x)上的祖先节点也会被更新到(因为深度更小),再次感谢wjyyy和creed_两位大佬指出我的错误

    正解应该是更新子树,将子树的最大值更新,查询照样,相比于我错误的代码只需改一句话

    代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cctype>
    #include <algorithm>
    #include <cmath>
    #define ll long long 
    #define ri register int 
    using namespace std;
    const int maxn=100005;
    const int inf=0x7fffffff;
    template <class T>inline void read(T &x){
      x=0;int ne=0;char c;
      while(!isdigit(c=getchar()))ne=c==‘-‘;
      x=c-48;
      while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
      x=ne?-x:x;
      return ;
    }
    int n,q;
    struct Edge{
      int ne,to;
    }edge[maxn<<1];
    int h[maxn],num_edge=0;
    inline void add_edge(int f,int t){
      edge[++num_edge].ne=h[f];
      edge[num_edge].to=t;
      h[f]=num_edge;
      return ;
    }
    int dep[maxn],fa[maxn],size[maxn],son[maxn],top[maxn],dfn[maxn],rnk[maxn],cnt=0;
    void dfs_1(int now){
      int v;size[now]=0;
      for(ri i=h[now];i;i=edge[i].ne){
          v=edge[i].to;
          if(v==fa[now])continue;
          fa[v]=now,dep[v]=dep[now]+1;
          dfs_1(v);
          size[now]+=size[v];
          if(!son[now]||size[son[now]]<size[v])son[now]=v;
      }
      return ;
    }
    void dfs_2(int now,int t){
      int v;top[now]=t;
      dfn[now]=++cnt,rnk[cnt]=now;
      if(!son[now])return ;
      dfs_2(son[now],t);
      for(ri i=h[now];i;i=edge[i].ne){
          v=edge[i].to;
          if(v==fa[now]||v==son[now])continue;
          dfs_2(v,v);
      }
      return ;
    }
    int mx[maxn<<2],L,R,dta;
    void build(int now,int l,int r){
      if(l==r){
         if(rnk[l]==1)mx[now]=1;
         else mx[now]=0;
         return ;
      }
      int mid=(l+r)>>1;
      build(now<<1,l,mid);
      build(now<<1|1,mid+1,r);
      if(dep[mx[now<<1]]>dep[mx[now<<1|1]]){
            mx[now]=mx[now<<1];
     }
     else mx[now]=mx[now<<1|1];
      return ;
    }
    void update(int now,int l,int r){
     if(L<=l&&r<=R){
         if(dep[mx[now]]<dep[dta]){
            mx[now]=dta;
         }
         return ;
     }
     int mid=(l+r)>>1;
     if(L<=mid)update(now<<1,l,mid);
     if(mid<R)update(now<<1|1,mid+1,r);
     if(dep[mx[now<<1]]>dep[mx[now<<1|1]]){
            mx[now]=mx[now<<1];
     }
     else mx[now]=mx[now<<1|1];
     return ;
    }
    int query(int now,int l,int r){
    if(L<=l&&r<=R){
        return mx[now];
    } 
    int mid=(l+r)>>1,ans=0,tmp;
    if(L<=mid){
       int tmp=query(now<<1,l,mid);
       if(dep[ans]<dep[tmp])ans=tmp;
    }
    if(mid<R){
       int tmp=query(now<<1|1,mid+1,r);
       if(dep[ans]<dep[tmp])ans=tmp;
    }
    return ans;
    }
    void update_path(int x){
     dta=x;
     //L=R=dfn[x];
     L=dfn[x],R=dfn[x]+size[x];
     update(1,1,n);
     return ;
    }
    int query_path(int x){
     int ans=0;
     while(top[x]!=1){
        L=dfn[top[x]],R=dfn[x];
        ans=query(1,1,n);
        if(ans!=0)return ans;
        x=fa[top[x]];
     }
     L=dfn[1],R=dfn[x];
     ans=query(1,1,n);
     return ans;
    }
    int main(){
      char opt[5];
      int x,y,z;
      read(n),read(q);
      for(ri i=1;i<n;i++){
         read(x),read(y);
         add_edge(x,y);
         add_edge(y,x);
      }
      dep[0]=-1,dep[1]=1,fa[1]=0;
      dfs_1(1);
      dfs_2(1,1);
      build(1,1,n);
      while(q--){
          //cout<<q<<endl;
          scanf("%s",opt);
          if(opt[0]==‘C‘){
              read(x);
              update_path(x);
          }
          else{
              read(x);
              printf("%d
    ",query_path(x));
          }
      }
      return 0;
    }
    

以上是关于luogu题解 P4092 [HEOI2016/TJOI2016]树树链剖分的主要内容,如果未能解决你的问题,请参考以下文章

暴力 p4092[HEOI2016/TJOI2016]树

[题解]luogu_P2824_HEOI2016排序(线段树/二分

luogu P2825 [HEOI2016/TJOI2016]游戏

BZOJ4552:[HEOI2016/TJOI2016]排序——题解

luogu P2824排序题解

HEOI2015小Z的房间