[yLCPC2024] F. PANDORA PARADOXXX(LCA+树上差分):经典复杂的删边问题=>连边问题

108 篇文章 0 订阅

[yLCPC2024] F. PANDORA PARADOXXX

题目背景

扶苏所在的城市的机厅联合举办了 KING of PerforPandora!

但是因为大雪封路,有些机厅不能到达。她想知道在能互相到达的机厅中距离最远为多少。

题目描述

给定一棵 n n n 个结点的树。一棵树被定义为一个有 n n n 个点和 n − 1 n-1 n1 条边的无向连通图。这棵树的边有边权。两点 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)\} cCmax{u,vcmaxdist(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 1q<n105),依次表示树的结点数和操作数。
接下来 n − 1 n - 1 n1 行,每行三个整数 u , v , w u,v,w u,v,w 1 ≤ u , v ≤ n 1 \leq u, v \leq n 1u,vn 1 ≤ w ≤ 1 0 5 1 \leq w \leq 10^5 1w105)表示树上有一条连接 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 1ei<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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

green qwq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值