• [ 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
      ",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
      ",q[i].ans);
        return 0;
      }
      
      
  • 相关阅读:
    Celery
    高并发架构
    websocket
    git分支管理
    auto_ptr与shared_ptr
    UDP信号驱动IO
    TCP带外数据
    UDP广播
    获取mac地址
    char数组初始化
  • 原文地址:https://www.cnblogs.com/SGCollin/p/10002225.html
Copyright © 2020-2023  润新知