BZOJ4379[POI2015] Modernizacja autostrady

30 篇文章 0 订阅
23 篇文章 1 订阅

BZOJ4379[POI2015] Modernizacja autostrady

Description

给定一棵无根树,边权都是1,请去掉一条边并加上一条新边,定义直径为最远的两个点的ju距离,请输出所有可能的新树的直径的最小值和最大值。

Input

第一行包含一个正整数n(3<=n<=500000),表示这棵树的点数。
接下来n-1行,每行包含两个正整数u,v(1<=u,v<=n),表示u与v之间有一条边。

Output

第一行输出五个正整数k,x1,y1,x2,y2,其中k表示新树直径的最小值,x1,y1表示这种情况下要去掉的边的两端点,x2,y2表示这种情况下要加上的边的两端点。

第二行输出五个正整数k,x1,y1,x2,y2,其中k表示新树直径的最大值,x1,y1表示这种情况下要去掉的边的两端点,x2,y2表示这种情况下要加上的边的两端点。

若有多组最优解,输出任意一组。

Sample Input

6

1 2

2 3

2 4

4 5

6 5

Sample Output

3 4 2 2 5

5 2 1 1 6

Solution:

网上的题解好像都是直接无脑dp的,好像只有我是推了性质去做的…

这个性质还是比较好推的吧。

先考虑最大值:

首先我们把直径拎出来,然后分断边在直径上和非直径上。

在直径上:

直接在直径上dp,一个直径点左边子树中直径的长度就是该点的外挂树的深度加上这点到直径左端点的距离或者是从左边一个点转移过来。右边同理。直接在直径上枚举这条断边,答案就是 D1+D2+1 。( D1D2 即是左边子树的直径和右边子树的直径)

不在直径上:

首先直径这一部分子树的直径肯定就直接是直径了,然后我们要使得剩下的子树直径最大,所以就直接断在与直径点相连的这一条边上,因为小的子树的直径肯定不会大于大的子树。于是直接每个点暴力dfs过去算一下就好了,复杂度 O(n)

再考虑最小值:

一样地分断边在直径上和断边不在直径上考虑。

在直径上:

一样的dp,答案就是 max(D1,D2,D12+D22+1) ,最后断点再dfs一下就可以求出来了。

不在直径上:

那么最后的直径最少也是原直径了,还不如不做操作(删边与添边相同)。

于是就能够解决这道题了,复杂度 O(n) 。代码写的有点难看。

#include<stdio.h>
#include<string.h>
#include<iostream>
#define M 500005
using namespace std;
struct Edge{int to,nxt;}Edge[M<<1];
int tot=0,Head[M],n;
void Addedge(int a,int b){
    Edge[++tot].to=b;Edge[tot].nxt=Head[a];Head[a]=tot;
    Edge[++tot].to=a;Edge[tot].nxt=Head[b];Head[b]=tot;
}
bool mark[M];
int mx,id,d[M],Q[M],sz,dp1[M],dp2[M];
void dfs1(int x,int fa,int dis){
    if(dis>mx)mx=dis,id=x;d[x]=dis;
    for(int i=Head[x];~i;i=Edge[i].nxt){
        int to=Edge[i].to;
        if(to==fa||mark[to])continue;
        dfs1(to,x,dis+1);
    }
}
void dfs2(int x,int dis){
    Q[++sz]=x;
    for(int i=Head[x];~i;i=Edge[i].nxt){
        int to=Edge[i].to;
        if(d[to]!=dis-1)continue;
        dfs2(to,dis-1);
        break;
    }
}
void solve(){
    mx=-1;dfs1(1,0,0);
    mx=-1;dfs1(id,0,0);
    int D=mx;sz=0;
    for(int i=1;i<=n;i++)
        if(d[i]==D){
            id=i;
            break;
        }
    dfs2(id,D);
    for(int i=1;i<=sz;i++){
        mark[Q[i-1]]=mark[Q[i+1]]=true;
        mx=-1;
        dfs1(Q[i],0,0);
        mark[Q[i-1]]=mark[Q[i+1]]=false;
        if(i+mx-1>dp1[i-1])dp1[i]=i+mx-1;
        else dp1[i]=dp1[i-1];
    }
    for(int i=sz;i>=1;i--){
        mark[Q[i-1]]=mark[Q[i+1]]=true;
        mx=-1;
        dfs1(Q[i],0,0);
        mark[Q[i-1]]=mark[Q[i+1]]=false;
        if(sz-i+mx>dp2[i+1])dp2[i]=sz-i+mx;
        else dp2[i]=dp2[i+1];
    }
    int id11,id12,id13,id14,id21,id22,id23,id24;
    int ans1=0,ans2=n+1;
    for(int i=1;i<sz;i++)
        if(dp1[i]+dp2[i+1]+1>ans1)ans1=dp1[i]+dp2[i+1]+1,id11=Q[i],id12=Q[i+1],id13=Q[1],id14=Q[sz];
    for(int i=1;i<=sz;i++){
        for(int j=Head[Q[i]];~j;j=Edge[j].nxt){
            int to=Edge[j].to;
            if(to==Q[i+1]||to==Q[i-1])continue;
            mark[Q[i]]=true;
            mx=-1;dfs1(to,Q[i],0);
            mx=-1;dfs1(id,0,0);
            mark[Q[i]]=false;
            if(D+mx+1>ans1)ans1=D+mx+1,id11=Q[i],id12=to,id13=id,id14=Q[1];
        }
    }
    for(int i=1;i<sz;i++){
        int t=(dp1[i]+1)/2+(dp2[i+1]+1)/2+1;
        t=max(t,dp1[i]);
        t=max(t,dp2[i+1]);
        if(t<ans2)ans2=t,id21=Q[i],id22=Q[i+1];
    }
    mark[id22]=true;
    mx=-1;dfs1(id21,id22,0);
    mx=-1;dfs1(id,0,0);
    sz=0;dfs2(id,mx);id23=Q[(sz+1)>>1];
    mark[id22]=false;

    mark[id21]=true;
    mx=-1;dfs1(id22,id21,0);
    mx=-1;dfs1(id,0,0);
    sz=0;dfs2(id,mx);id24=Q[(sz+1)>>1];
    mark[id21]=false;

    printf("%d %d %d %d %d\n",ans2,id21,id22,id23,id24);
    printf("%d %d %d %d %d\n",ans1,id11,id12,id13,id14);
}
int main(){
    memset(Head,-1,sizeof(Head));
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int a,b;
        scanf("%d %d",&a,&b);
        Addedge(a,b);
    }
    solve();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值