[BZOJ4379][POI2015]Modernizacja autostrady[树的直径+换根dp]

题意

给定一棵 \(n\) 个节点的树,可以断掉一条边再连接任意两个点,询问新构成的树的直径的最小和最大值。

\(n\leq 5\times 10^5\) .

分析

  • 记断掉一条边之后两棵树的直径为 \(A,B\) ,最长直径为 \(A+B+1\) 最短为 \(\max\{A\ ,B\ ,\lceil \frac{A}{2}\rceil+\lceil \frac{B}{2} \rceil +1\}\) .

  • 维护每个点不同子树的前3长链和向上的最长链、不同子树的前2长路径和向上子树的最长路径。

  • 这样枚举断掉一条边之后换根 \(dp\) 一下就可以求得父节点所在树的直径了。

  • 方案可以按照 \(dfs\) 找树的直径的方式构造答案。

  • 总时间复杂度为 \(O(n)\)

换根dp可以解决树断一条边以及不同根询问等问题。

代码

#include<bits/stdc++.h>
using namespace std;
#define go(u) for(int i=head[u],v=e[i].to;i;i=e[i].last,v=e[i].to)
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define pb push_back
#define re(x) memset(x,0,sizeof x)
typedef long long LL;
inline int gi(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-48;ch=getchar();}
    return x*f;
}
template<typename T>inline bool Max(T &a,T b){return a<b?a=b,1:0;}
template<typename T>inline bool Min(T &a,T b){return b<a?a=b,1:0;}
const int N=5e5 + 7,inf=0x3f3f3f3f;
int n,edc=1,edg1,edg2,ans1=inf,ans2,ban[N<<1];
int f1[N][3],f2[N],g1[N][2],g2[N],fu[N],f[N][4],g[N][3],head[N];
struct edge{
    int last,to;
    edge(){}edge(int last,int to):last(last),to(to){}
}e[N*2];
void Add(int a,int b){
    e[++edc]=edge(head[a],b),head[a]=edc;
    e[++edc]=edge(head[b],a),head[b]=edc;
}
bool cmp(int x,int y){return x>y;}
void dfs1(int u,int fa){
    go(u)if(v^fa){
        dfs1(v,u);
        int x=fu[v];
        if(x>g1[u][0]) g1[u][1]=g1[u][0],g1[u][0]=x;
        else if(x>g1[u][1]) g1[u][1]=x;
        
        x=f1[v][0]+1;
        if(x>f1[u][0]) f1[u][2]=f1[u][1],f1[u][1]=f1[u][0],f1[u][0]=x;
        else if(x>f1[u][1]) f1[u][2]=f1[u][1],f1[u][1]=x;
        else if(x>f1[u][2]) f1[u][2]=x;
    }
    fu[u]=max(g1[u][0],f1[u][0]+f1[u][1]);
}
void dfs2(int u,int fa){
    for(int i=0;i<3;++i) f[u][i]=f1[u][i];f[u][3]=f2[u];sort(f[u],f[u]+4,cmp);
    for(int i=0;i<2;++i) g[u][i]=g1[u][i];g[u][2]=g2[u];sort(g[u],g[u]+3,cmp);
    go(u)if(v^fa){
        int x=0,cnt=0;
        for(int j=0,fg=0;j<4;++j){
            if(f[u][j]==f1[v][0]+1&&!fg){fg=1;continue;}
            x=f[u][j]; break;
        }
        f2[v]=x+1,x=0;
        for(int j=0,fg=0;j<4;++j){
            if(f[u][j]==f1[v][0]+1&&!fg) {fg=1;continue;}
            x+=f[u][j];if(++cnt==2) break;
        }
        
        for(int j=0,fg=0;j<3;++j){
            if(g[u][j]==fu[v]&&!fg) {fg=1;continue;}
            Max(x,g[u][j]);break;
        }
        g2[v]=x;
        
        int tmp=max(max(x,fu[v]),(x+1)/2+(fu[v]+1)/2+1);
        if(ans1>tmp) ans1=tmp,edg1=i;
        
        tmp=(x+fu[v]+1);
        if(ans2<tmp) ans2=tmp,edg2=i;
        
        dfs2(v,u);
    }
}
int fa[N],mx,cho,tp,tmp[N];
void dfs(int u,int dis){
    if(dis>=mx) mx=dis,cho=u;
    go(u)if(v^fa[u]&&!ban[i]) fa[v]=u,dfs(v,dis+1);
}
int main(){
    n=gi();
    rep(i,1,n-1) Add(gi(),gi());
    dfs1(1,0); dfs2(1,0);
    
    printf("%d %d %d",ans1,e[edg1].to,e[edg1^1].to);
    ban[edg1]=ban[edg1^1]=1;
    int x=e[edg1].to;dfs(x,0);
    re(fa),x=cho,mx=0,dfs(x,0);tp=0;for(int i=cho;i;i=fa[i]) tmp[++tp]=i;printf(" %d",tmp[tp+1>>1]);
    
    re(fa),x=e[edg1^1].to,mx=0,dfs(x,0);
    re(fa),x=cho,mx=0,dfs(x,0);tp=0;for(int i=cho;i;i=fa[i]) tmp[++tp]=i;printf(" %d\n",tmp[tp+1>>1]);
    
    printf("%d %d %d",ans2,e[edg2].to,e[edg2^1].to);
    ban[edg1]=ban[edg1^1]=0,ban[edg2]=ban[edg2^1]=1;
    
    re(fa),x=e[edg2].to,mx=0,dfs(x,0);printf(" %d",cho);
    re(fa),x=e[edg2^1].to,mx=0,dfs(x,0);printf(" %d\n",cho);
    return 0;
}

转载于:https://www.cnblogs.com/yqgAKIOI/p/9838677.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值