1 条题解
-
0
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
信息
- ID
- 2416
- 时间
- 6000ms
- 内存
- 1024MiB
- 难度
- 9
- 标签
- 递交数
- 9
- 已通过
- 4
- 上传者