[yLCPC2024] F. PANDORA PARADOXXX
题目背景
扶苏所在的城市的机厅联合举办了 KING of PerforPandora!
但是因为大雪封路,有些机厅不能到达。她想知道在能互相到达的机厅中距离最远为多少。
题目描述
给定一棵 n n n 个结点的树。一棵树被定义为一个有 n n n 个点和 n − 1 n-1 n−1 条边的无向连通图。这棵树的边有边权。两点 u , v u,v u,v 间的距离 d i s t ( u , v ) \mathrm{dist}(u,v) dist(u,v) 定义为从 u u u 到 v v v 的简单路径边权和。可以证明树上两点间的简单路径是唯一的。特别的,我们规定 d i s t ( u , u ) = 0 \mathrm{dist}(u, u) = 0 dist(u,u)=0。
现在有 q q q 次操作,每次会删除这棵树上的一条边。显然在做出至少一次操作后,这棵树会被分成若干个连通块。你需要在每次操作后都求出每个连通块内距离最远的两个点的距离的最大值。
形式化的,每次操作后,你要求出
max c ∈ C { max u , v ∈ c d i s t ( u , v ) } \max\limits_{c \in C}\{\max\limits_{u, v \in c} \mathrm{dist}(u,v)\} c∈Cmax{u,v∈cmaxdist(u,v)}
其中 C C C 表示当前所有连通块构成的集合。
输入格式
本题单个测试点内有多组测试数据。第一行是一个正整数 T T T,表示数据组数。对每组数据:
输入第一行是两个整数
n
,
q
n, q
n,q(
1
≤
q
<
n
≤
1
0
5
1 \leq q < n \leq 10^5
1≤q<n≤105),依次表示树的结点数和操作数。
接下来
n
−
1
n - 1
n−1 行,每行三个整数
u
,
v
,
w
u,v,w
u,v,w(
1
≤
u
,
v
≤
n
1 \leq u, v \leq n
1≤u,v≤n,
1
≤
w
≤
1
0
5
1 \leq w \leq 10^5
1≤w≤105)表示树上有一条连接
u
u
u 和
v
v
v 的权值为
w
w
w 的边。
接下来
q
q
q 行,每行一个整数
e
i
e_i
ei(
1
≤
e
i
<
n
1 \leq e_i < n
1≤ei<n),表示这次操作删除了输入的第
e
i
e_i
ei 条边。数据保证每条边只会被删除一次。
数据保证单个测试点内的 n n n 之和不超过 3 × 1 0 5 3 \times 10^5 3×105。
输出格式
对每组数据,输出 q q q 行,每行一个整数,依次表示每次操作后所求的答案。
样例 #1
样例输入 #1
2
4 2
1 2 1
2 3 2
3 4 3
2
3
12 2
1 2 1
2 3 1
1 4 3
2 5 4
5 6 3
5 7 2
7 8 1
8 9 1
9 10 1
7 11 5
8 12 3
4
6
样例输出 #1
3
1
10
9
提示
提示
请注意大量的数据读入和输出对程序效率造成的影响,选择合适的读入输出方式,不要频繁刷新输出缓冲区,避免超时。
思路
- 经典的删边问题,因为如果你真正去删边的话,这道题会变得很复杂,因此,为了简化计算,我们就把删边问题转化成连边问题。
- 那么具体怎么做呢?我们可以:
对于需要合并的两个连通块 x , y x,y x,y,其合并之后的最远点对距离一定是合并之前的两个连通块的最远点对中产生的。在合并的时候枚举最远点对,取距离最大值即可。由于我们是倒着来的,所有连通块的最远点对距离最大值不减,所以能直接在合并之后取最大值。
维护连通块用并查集即可。
对于两个连通块合并之后最远点对必在两个连通块最远点对中出现的证明:
设两个连通块通过边 ( x , y ) (x,y) (x,y) 连通,两个连通块内最远点对分别为 ( a , b ) , ( c , d ) (a,b),(c,d) (a,b),(c,d)。若最远点对不经过边 ( x , y ) (x,y) (x,y),则一定是两个连通块中最远点对距离较大的一组。若最远点对经过 ( x , y ) (x,y) (x,y),由于与 x x x 距离最远的点为 a , b a,b a,b 中的一个,与 y y y 距离最远的点为 c , d c,d c,d 中的一个。所以最远点对存在于 { a , b , c , d } \{a,b,c,d\} {a,b,c,d} 中。
- 总的来说,本道题最基本的思路就是:先预处理出从每个点到所有点的最大距离,然后我们再把不需要删的边先合并了。
更具体的思路:
所以我们代码中的
s
s
s 数组就是来维护上图的
x
,
y
,
p
,
q
x,y,p,q
x,y,p,q 四点。
代码
//经典套路删边变加边=>删边问题往往转化成加边问题
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int N = 3e5+10,M = 2*N;
int e[M],ne[M],w[M],h[N],idx;
struct oo{
int x,y,w;
}E[N];
int T,n,q;
int f[N][25],dist[N];
int s[N][2],depth[N];
int p[N],d[N];
bool st[N];
int Mx[N],ans[N];
int nowM;
int find(int x){
if(x!=p[x])p[x]=find(p[x]);
return p[x];
}
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u,int fa){
depth[u]=depth[fa]+1;
f[u][0]=fa;
for(int i=1;i<=23;i++)f[u][i]=f[f[u][i-1]][i-1];
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa)continue;
dist[j]=dist[u]+w[i];
dfs(j,u);
}
}
int lca(int a,int b){
if(depth[a]<depth[b])swap(a,b);
for(int i=23;i>=0;i--){
if(depth[f[a][i]]>=depth[b]){
a=f[a][i];
}
}
if(a==b)return a;
for(int i=23;i>=0;i--){
if(f[a][i]!=f[b][i]){
a=f[a][i];
b=f[b][i];
}
}
return f[a][0];
}
void merge(int x,int y){
p[y]=x;
int maxv=0;
int now[]={s[x][0],s[x][1],s[y][0],s[y][1]};
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
int a=now[i],b=now[j];
int dis=dist[a]+dist[b]-2*dist[lca(a,b)];
if(dis>maxv){
maxv=dis;
s[x][0]=a,s[x][1]=b;
Mx[x]=dis;
}
}
}
nowM=max(nowM,Mx[x]);
}
signed main(){
scanf("%lld",&T);
while(T--){
scanf("%lld%lld",&n,&q);
// memset(h,-1,sizeof h);
idx=0;
nowM=0;
for(int i=0;i<=n+q+1;i++)h[i]=-1;
for(int i=1;i<=n;i++){
s[i][0]=s[i][1]=0;
s[i][1]=s[i][0]=i;
depth[i]=0;
for(int j=0;j<=24;j++)f[i][j]=0;
Mx[i]=dist[i]=0;
st[i]=0;
}
for(int i=1;i<n;i++){
int a,b,c;
scanf("%lld%lld%lld",&a,&b,&c);
add(a,b,c),add(b,a,c);
E[i]={a,b,c};
}
dfs(1,-1);//预处理depth和f数组(LCA板子)
// for(int i=1;i<n;i++)cout<<dist[i]<<' ';
for(int i=1;i<=n;i++)p[i]=i;
for(int i=1;i<=q;i++){
scanf("%lld",&d[i]);
st[d[i]]=true;
}
for(int i=1;i<n;i++){//先把不需要删的边先连接起来求个最大值
if(st[i])continue;
int x=E[i].x,y=E[i].y;
if(depth[x]>depth[y])swap(x,y);
merge(find(x),find(y));
}
for(int i=q;i>=1;i--){
int x=E[d[i]].x,y=E[d[i]].y;
if(depth[x]>depth[y])swap(x,y);
ans[i]=nowM;
// cout<<nowM<<endl;
// cout<<"-------//-----"<<endl;
merge(find(x),find(y));
}
for(int i=1;i<=q;i++){
printf("%lld\n",ans[i]);
}
}
return 0;
}