[ HEOI 2016 ] 树

\(\\\)

Description


给出一颗树,开始只有 \(1\) 号节点有标记。

  • \(\ C\ x\)\(x\) 号节点打标记

  • \(\ Q\ x\) 查询 \(x\) 号节点深度最深的有标记的祖先

\(\\\)

Solution


  • 链剖做法:

    查询直到跳到第一个有权的重链上,线段树上二分即可。太板了不说了。

  • DFS序+线段树做法:

    一遍DFS求出DFS序,子树大小以及节点深度。

    用线段树维护DFS序,每个节点记录覆盖当前区间深度最深的节点编号。标记下放的时候只需选择深度更深的作为答案即可。注意设置根节点的深度,否则第一次的全局标记可能会无效。

    因为DFS序中一棵子树是连续的,所以标记可以看作整棵子树的区间覆盖操作。查询也很方便,在递归查找时下放标记即可。

    #include<cmath>
    #include<cstdio>
    #include<cctype>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define N 100010
    #define gc getchar
    #define Rg register
    #define mid ((l+r)>>1)
    using namespace std;
    
    inline int rd(){
      int x=0; bool f=0; char c=gc();
      while(!isdigit(c)){if(c=='-')f=1;c=gc();}
      while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
      return f?-x:x;
    }
    
    int n,m,tot,hd[N];
    
    int cnt,s[N],d[N],dfn[N],sz[N];
    
    struct edge{int to,nxt;}e[N];
    
    inline void add(int u,int v){
      e[++tot].to=v; e[tot].nxt=hd[u]; hd[u]=tot;
    }
    
    void dfs(int u,int fa){
      dfn[u]=++cnt;
      s[cnt]=u; sz[u]=1;
      for(Rg int i=hd[u],v;i;i=e[i].nxt)
        if((v=e[i].to)!=fa){
          d[v]=d[u]+1;
          dfs(v,u);
          sz[u]+=sz[v];
        }
    }
    
    struct segment{
    
      int root,ptr;
    
      inline int newnode(){return ++ptr;}
    
      struct node{int ls,rs,tag;}c[N<<1];
    
      inline void build(int &rt,int l,int r){
        rt=newnode();
        if(l==r) return;
        build(c[rt].ls,l,mid);
        build(c[rt].rs,mid+1,r);
      }
    
      inline void pushdown(int rt){
        if(d[c[rt].tag]>d[c[c[rt].ls].tag]) c[c[rt].ls].tag=c[rt].tag;
        if(d[c[rt].tag]>d[c[c[rt].rs].tag]) c[c[rt].rs].tag=c[rt].tag;
        c[rt].tag=0;
      }
    
      inline void updata(int rt,int l,int r,int L,int R,int p){
        if(l>R||r<L) return;
        if(l>=L&&r<=R){
          if(d[p]>d[c[rt].tag]) c[rt].tag=p;
          return;
        }
        if(c[rt].tag) pushdown(rt);
        if(L<=mid) updata(c[rt].ls,l,mid,L,R,p);
        if(R>mid) updata(c[rt].rs,mid+1,r,L,R,p);
      }
    
      inline int query(int rt,int l,int r,int p){
        if(l==r) return c[rt].tag;
        if(c[rt].tag) pushdown(rt);
        if(p<=mid) return query(c[rt].ls,l,mid,p);
        else return query(c[rt].rs,mid+1,r,p);
      }
    
    }tree;
    
    int main(){
      n=rd(); m=rd();
      for(Rg int i=1,u,v;i<n;++i){
        u=rd(); v=rd();
        add(u,v); add(v,u);
      }
      d[1]=1; dfs(1,0);
      tree.build(tree.root,1,n);
      tree.updata(tree.root,1,n,1,n,1);
      char c; int x;
      while(m--){
        c=gc(); while(!isalpha(c)) c=gc();
        if(c=='Q') printf("%d\n",tree.query(tree.root,1,n,dfn[rd()]));
        else{x=rd();tree.updata(tree.root,1,n,dfn[x],dfn[x]+sz[x]-1,x);}
      }
      return 0;
    }
    
    
  • 并查集做法:

    我们用并查集指针代表当前最近的标记节点的方向,开始都指向父节点。标记一个点只需直接将指针指向自己,查询即找到集合代表元素。容易发现正着处理复杂度不对,因为要保证树的形态正确,所以我们不能使用并查集的优化方式。

    时光倒流。开始先把所有的标记打上,其余的点指向父节点。倒着模拟,遇到打标记就撤销标记,询问就是找到集合代表元素。可以使用路径压缩优化,因为只会撤销标记,任意时刻集合的代表元素必然是集合内的最优答案。

    注意一个节点可能被多次打标记,在最后一次撤销标记时我们再将其与父节点 merge 在一起。

    #include<cmath>
    #include<queue>
    #include<cstdio>
    #include<cctype>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define N 100010
    #define R register
    #define gc getchar
    #define mid ((l+r)>>1)
    using namespace std;
    
    inline int rd(){
      int x=0; bool f=0; char c=gc();
      while(!isdigit(c)){if(c=='-')f=1;c=gc();}
      while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
      return f?-x:x;
    }
    
    int n,m,tot,hd[N],fa[N],cnt[N];
    
    struct edge{int to,nxt;}e[N<<1];
    
    inline void add(int u,int v){
      e[++tot].to=v; e[tot].nxt=hd[u]; hd[u]=tot;
    }
    
    void dfs(int u){
      for(R int i=hd[u],v;i;i=e[i].nxt)
        if((v=e[i].to)!=fa[u]){fa[v]=u;dfs(v);}
    }
    
    struct Q{int op,x,ans;}q[N];
    
    struct UFS{
      int f[N];
      UFS(){memset(f,0,sizeof(f));}
      int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
      inline void merge(int x,int fa){f[find(x)]=find(fa);}
    }ufs;
    
    int main(){
      n=rd(); m=rd();
      for(R int i=1,u,v;i<n;++i){
        u=rd(); v=rd();
        add(u,v); add(v,u);
      }
      fa[1]=1;
      dfs(1); char c;
      for(R int i=1;i<=m;++i){
        c=gc(); while(!isalpha(c)) c=gc();
        q[i].op=(c=='C'); q[i].x=rd();
        if(q[i].op){ufs.f[q[i].x]=q[i].x;++cnt[q[i].x];}
      }
      for(R int i=1;i<=n;++i) if(!ufs.f[i]) ufs.f[i]=fa[i];
      for(R int i=m;i;--i)
        if(q[i].op){
          --cnt[q[i].x];
          if(!cnt[q[i].x]) ufs.merge(q[i].x,fa[q[i].x]);
        }
        else q[i].ans=ufs.find(q[i].x);
      for(R int i=1;i<=m;++i) if(!q[i].op) printf("%d\n",q[i].ans);
      return 0;
    }
    
    
posted @ 2018-11-22 17:02  SGCollin  阅读(75)  评论(0编辑  收藏