D157 最近公共祖先+树链剖分+数状数组 P4949 最短距离
D157 最近公共祖先+树链剖分+数状数组 P4949 最短距离_哔哩哔哩_bilibili
n 个点 n 条边的无向连通图, m 次操作。支持两种操作:1. 修改 第 x 条边的长度为 y;2. 查询 点 x 到点 y 的最短距离。
思路
n 个点 n 条边的无向连通图就是基环树(即环套树),直接考虑不好做
如果砍掉环上的一条边,变成一棵树,该怎样做呢?
树上求两点之间的距离,经典做法就是 $d(x,y)=d[x]+d[y]−2∗d[lca]$,其中 d[i] 为各点到根的距离,可以预处理
修改边权 $(x,y)$ 操作,实际上会影响以 $y$ 为根的子树内每个点的 $d$ 值,是区间修改
经典做法就是用树链剖分,把树上区间转化为线性区间,然后用线段树或树状数组,$logn$ 的时间维护 $d$ 值的区间修改
然后,考虑环上的那条断边对修改和查询有什么影响?
做树链剖分时,顺便找出环上的一条边断掉,记住断边 $(u,v,w)$
如果修改的是断边,直接更改其边权;如果不是断边,用 树状数组 维护区间修改,就在区间两端做一下增量的差分
如果是查询 $(x,y)$,答案是 $min \{ d(x,y),\;d(x,u)+d(y,v)+w,\;d(x,v)+d(y,u)+w \}$,如图
相关板子:
C81【模板】树状数组 点修+区查 区修+点查 - 董晓 - 博客园
D28 基环树 树形DP P2607 [ZJOI2008] 骑士 - 董晓 - 博客园
D29 基环树 树的直径 P1399 [NOI2013] 快餐店 - 董晓 - 博客园
D30 基环树 遍历最小字典序 P5022 [NOIP2018 提高组] 旅行 - 董晓 - 博客园
// 最近公共祖先+树链剖分+数状数组 #include<bits/stdc++.h> #define ll long long using namespace std; const int N=100010; int idx=1,h[N],to[N<<1],ww[N<<1],ne[N<<1],del; void add(int x,int y,int z){ to[++idx]=y;ww[idx]=z;ne[idx]=h[x];h[x]=idx; } struct node{ int x,y,z,id; //给每一条边编号 }e[N]; int n,m; int dfn[N],siz[N],dep[N],son[N],fa[N],top[N]; ll d[N]; bool vis[N]; void dfs1(int x,int f){ //树链剖分 更新dep,fa,son,siz,d,e dep[x]=dep[f]+1; fa[x]=f; siz[x]=1; for(int i=h[x]; i; i=ne[i]){ int y=to[i]; if(y==f) continue; if(vis[y]){del=i>>1; continue;} //del记录那条断环边 vis[y]=true; e[i>>1].id=i; //记录树边的编号i/2 d[y]=d[x]+ww[i]; //d:记录y点到根的距离 dfs1(y,x); siz[x]+=siz[y]; if(siz[y]>siz[son[x]]) son[x]=y; } } void dfs2(int x,int t){ //树链剖分 更新dfn,top dfn[x]=++dfn[0]; //dfs序,用于BIT的下标 top[x]=t; if(son[x]) dfs2(son[x],t); //搜重儿子 for(int i=h[x]; i; i=ne[i]){ int y=to[i]; if(y==fa[x]||y==son[x]|| y==e[del].x&&x==e[del].y|| y==e[del].y&&x==e[del].x) continue; //排除断环边 dfs2(y,y); //搜轻儿子 } } int lca(int x,int y){ //求lca while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); x=fa[top[x]]; } return dep[x]>dep[y]?y:x; } struct BIT{ //数状数组 ll s[N]; void upd(int x,int C){ //点更新 for(;x<=n;x+=x&-x) s[x]+=C; } void upd(int x,int y,int C){ //差分更新 upd(x,C); upd(y+1,-C); } void change(int u,int C){ //区间更新 upd(dfn[u], dfn[u]+siz[u]-1, C); } ll ask(int x){ //前缀和 ll sum=0; for(;x;x-=x&-x) sum+=s[x]; return sum; } ll dis(int u,int v){ //两点之间的距离 return ask(dfn[u])+ask(dfn[v])-2*ask(dfn[lca(u,v)]); } }B; int main(){ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); cin>>n>>m; for(int i=1,x,y,z; i<=n; i++){ cin>>x>>y>>z; add(x,y,z); add(y,x,z); e[i]={x,y,z,0}; } vis[1]=true; //标记访问1 dfs1(1,0); dfs2(1,1); //树链剖分 for(int i=1; i<=n; i++) B.upd(dfn[i],dfn[i],d[i]); //初始化BIT for(int op,x,y;m--;){ cin>>op>>x>>y; if(op==1){ if(x==del){ e[x].z=y; //对断环边只更新边权 continue; } int dy=y-e[x].z; //把修改的边权转化为增加量 e[x].z=y; //记录新边权 int v=to[e[x].id]; //第x条边的终点 B.change(v,dy); //v子树增加点权 } else{ ll d1=B.dis(x,y); ll d2=B.dis(x,e[del].x)+B.dis(y,e[del].y)+e[del].z; ll d3=B.dis(x,e[del].y)+B.dis(y,e[del].x)+e[del].z; cout<<min({d1,d2,d3})<<'\n'; } } }
浙公网安备 33010602011771号