题解:P16826 [AFOI 2025] C.道路修复
haoertiansuo · · 题解
在线纯并查集写法。
不带修的话答案显然是每个点到直径两端点的距离最大值,这部分用经典做法,以距离上一个点最远的位置为根 DFS 三遍即可。
接下来考虑带修。我们发现操作相当于将以
发现这么修改后菊花图中的每个点可以跳到
接下来考虑如何实现。我们可以以
现在的问题是怎么判断包含
- 如果
v 的 DFS 序区间包含了x ,那么x 是v 的内子树,只需要判断x 是否包含了u ; - 否则
x 是v 的外子树,只需要判断u 是否也在外子树内(即v 不包含u )。
注意到缩菊花图后树的结构可能发生改变,但如果
因为只能用路径压缩,所以时间复杂度为
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500000;
int i,j,k,n,m,t,rt,it,l[N+50],r[N+50],F[N+50];
ll g[N+50],f[N+50][3],res[N+50],t1,t2;
vector<pair<int,int> > v[N+50];
int find(int x){
if(F[x]!=x){
int k=find(F[x]); g[x]+=g[F[x]]; F[x]=k;
}
return F[x];
}
void dfs0(ll d,int x,int fa){
l[x]=++it;
if(d>t2){t1=x; t2=d;}
res[x]=max(res[x],d);
for(auto [y,w]:v[x])if(y!=fa)dfs0(d+w,y,x);
r[x]=it;
}
int _y;
void dfs2(ll d,int x,int fa){
if(f[x][1]+d>=f[_y][1]){
f[_y][2]=f[_y][1];
f[_y][1]=f[x][1]+d; f[_y][0]=f[x][0];
}
else f[_y][2]=max(f[_y][2],f[x][1]+d);
f[_y][2]=max(f[_y][2],f[x][2]+d);
g[x]=d; F[x]=_y;
for(auto [y,w]:v[x])if(y!=fa){
dfs2(d+w,y,x);
}
v[x]={};
}
void upd(int x,int y){
_y=y;
t1=t2=0;
for(auto [z,w]:v[y]){
if(l[y]<=l[z]&&r[z]<=r[y]){
if(l[z]<=l[x]&&r[x]<=r[z]){t1=z; t2=w; continue;}
}
else{
if(l[x]<l[y]||l[x]>r[y]){t1=z; t2=w; continue;}
}
dfs2(w,z,y);
}
if(t1)v[y]={{t1,t2}};
else v[y]={};
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin>>n;
for(i=1;i<=n;i++){
F[i]=i;
f[i][0]=i; f[i][1]=0; f[i][2]=-1e18;
}
for(i=1;i<n;i++){
cin>>j>>k>>m;
v[j].push_back({k,m});
v[k].push_back({j,m});
}
rt=1;
for(int T=1;T<=3;T++){it=0; t1=rt; t2=0; dfs0(0,rt,0); rt=t1;}
cin>>t;
while(t--){
int op,x,y;
cin>>op>>x;
if(op==1){
cin>>y; upd(x,y);
}
else{
k=find(x);
cout<<max(res[x],g[x]+(f[k][0]==x?f[k][2]:f[k][1]))<<'\n';
}
}
}