1 条题解

  • 0
    @ 2026-3-10 23:05:56

    D60 树的直径 树剖+树状数组+线段树 P6845 [CEOI 2019] Dynamic Diameter

    // 树剖+树状数组+线段树 O(qlognlogn)
    #include<bits/stdc++.h>
    using namespace std;
    
    long long read(){
      long long x=0; int f=0; char c=getchar();
      while(!isdigit(c)) f|=c=='-',c=getchar();
      while(isdigit(c)) x=x*10+c-'0',c=getchar();
      return f?-x:x;
    }
    
    const int N=100005;
    int n,q,idx=1,hd[N],to[N<<1],ne[N<<1],go[N];
    long long W,w[N],d[N];
    void add(){
      int a=read(),b=read();
      to[++idx]=b,ne[idx]=hd[a],hd[a]=idx;
      to[++idx]=a,ne[idx]=hd[b],hd[b]=idx;
      w[idx>>1]=read(); //记录边权,保证正反边除2后是同一条边的编号{2,3}/2=1,{4,5}/2=2
    }
    
    //树剖: 预处理原树节点的 LCA; DFS序; rnk
    int dep[N],fa[N],top[N],siz[N],son[N],dfn[N],rnk[N];
    void dfs1(int x,int f){
      fa[x]=f; dep[x]=dep[f]+1; siz[x]=1;
      for(int y,i=hd[x];i;i=ne[i])if((y=to[i])!=f){
        d[y]=d[x]+w[i>>1]; //记录从根到y的距离
        dfs1(y,x);
        siz[x]+=siz[y];
        if(siz[son[x]]<siz[y]) son[x]=y;
        go[i>>1]=y; //记录当前边的出点是y。边权下移给点权,映射为树状数组和线段树,方便区修
      }
    }
    void dfs2(int x,int t){
      top[x]=t; dfn[x]=++dfn[0]; rnk[dfn[0]]=x; //链顶; DFS序; DFS序的逆映射
      if(son[x]) dfs2(son[x],t);
      for(int y,i=hd[x];i;i=ne[i])if((y=to[i])!=fa[x] && y!=son[x]) dfs2(y,y);
    }
    int lca(int x,int y){
      while(top[x]!=top[y]) dep[top[x]]>dep[top[y]]?(x=fa[top[x]]):(y=fa[top[y]]);
      return dep[x]<dep[y]?x:y;
    }
    
    struct BIT{ //树状数组维护点权的差分、前缀和、求直径,节点对应 DFS序
      long long c[N];
      void upd(int x,long long k){ //点加
        for(;x<=n;x+=x&-x) c[x]+=k;
      }
      void upd(int x,int y,long long w){ //差分点加
        upd(x,w), upd(y+1,-w);
      }
      long long ask(int x){ //求前缀和
        long long s=d[rnk[x]];
        for(;x;x-=x&-x) s+=c[x];
        return s;
      }
      long long dis(pair<int,int> a){ //求直径
        return ask(dfn[a.first])+ask(dfn[a.second])-2*ask(dfn[lca(a.first,a.second)]);
      }    
    }B;
    
    struct SGT{ //线段树维护子树区间的直径端点,节点对应 DFS序
      #define ls (u<<1)
      #define rs (u<<1|1)
      #define mid ((l+r)>>1)
      #define pii pair<int,int>
      pii pt[N<<2]; //保存每颗子树的直径的两个端点
      
      pii pushup(const pii& a, const pii& b){ //合并两子树的直径端点
        pii p[6]={a,b,{a.first,b.first},{a.first,b.second},
                      {a.second,b.first},{a.second,b.second}};
        long long d[6];
        for(int i=0;i<6;i++) d[i]=B.dis(p[i]); //计算每对点之间的距离
        return p[max_element(d,d+6)-d]; //查找指定范围内的最大元素(即直径的端点)
      }
      
      void build(int u=1,int l=1,int r=n){ //建线段树
        if(l==r) return pt[u]={rnk[l],rnk[l]},void();
        build(ls,l,mid),build(rs,mid+1,r);
        pt[u]=pushup(pt[ls],pt[rs]);
      }
      void upd(int x,int y,int u=1,int l=1,int r=n){ //区修
        if(x>r || y<l) return;   //越界返回
        if(x<=l && r<=y) return; //覆盖立刻返回,保证一次区修为logn。因为子树内部的直径不受子树根权的影响
        upd(x,y,ls,l,mid);
        upd(x,y,rs,mid+1,r);
        pt[u]=pushup(pt[ls],pt[rs]); //直径的两个端点维护到树根上
      }
    }S;
    
    signed main(){
      n=read(),q=read(),W=read(); //点的个数,询问的个数,边权的上限
      for(int i=1;i<n;i++) add();    
      dfs1(1,0);
      dfs2(1,1); //树剖
      S.build(); //建线段树
      long long last=0;
      while(q--){
        int i=(read()+last)%(n-1)+1; //第 i 条边
        long long v=(read()+last)%W; //新边权
        B.upd(dfn[go[i]], dfn[go[i]]+siz[go[i]]-1, v-w[i]); //更新边权:边权给点权,原点权w加(v-w),即修改为v
        w[i]=v; //将第 i 条边的边权改为 v
        S.upd(dfn[go[i]], dfn[go[i]]+siz[go[i]]-1); //更新直径端点
        printf("%lld\n",last=B.dis(S.pt[1]));       //计算新直径长度
      }
    }
    
    • 1

    D60 树的直径 树剖+树状数组+线段树「CEOI2019」动态直径

    信息

    ID
    2416
    时间
    6000ms
    内存
    1024MiB
    难度
    9
    标签
    递交数
    9
    已通过
    4
    上传者