P3761 [TJOI2017]城市

题目链接

从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作。这个地区一共有 n 座城市,n-1 条高速公路,保证了任意两运城市之间都可以通过高速公路相互可达,但是通过一条高速公路需要收取一定的交通费用。小明对这个地区深入研究后,觉得这个地区的交通费用太贵。

小明想彻底改造这个地区,但是由于上司给他的资源有限,因而小明现在只能对一条高速公路进行改造,改造的方式就是去掉一条高速公路,并且重新修建一条一样的高速公路(即交通费用一样),使得这个地区的两个城市之间的最大交通费用最小(即使得交通费用最大的两座城市之间的交通费用最小),并且保证修建完之后任意两座城市相互可达。如果你是小明,你怎么解决这个问题?

题意:从树上删除一条边,然后建一条新边,边权为删掉的边的边权,使得仍然是一棵树,且使得树的直径最短,输出最短的直径的答案。

思路:既然要使得直径最短,那么先从未改变的树入手,如果删除的不是树的直径上的边,那么答案只可能变大,因为之前直径的值没变,那么为了将直径变小,我们可以采用依次删除直径上的边的方法来得到答案,那既然删除了边,应该连到哪里最合适,肯定是连接两个连通块的中心获得的答案最小,那么可以分别跑出两个连通块的中心到离它最远点的距离加上新建边的边权与两个连通块的直径取max得到答案即可。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
struct tt{
    int x,to,val;
};
tt e[1000010];
int idx=0,h[5010];
void add(int a,int b,int c){
    e[idx].x=b,e[idx].val=c,e[idx].to=h[a],h[a]=idx++;
}
int ans,pos;
int vis[5010];//标记重心
int st[5010];
int flag[1000010];//标记直径上路线
int n;
vector<int>ve;
void dfs(int x,int fa,int step){
    if(step>ans){
        ans=step;
        pos=x;
    }
    for(int i=h[x];i!=-1;i=e[i].to){
        if(flag[i])continue;
        int j=e[i].x;
        if(j==fa)continue;
        dfs(j,x,step+e[i].val);
    }
}
void ddfs(int x,int fa){
    for(int i=h[x];i!=-1;i=e[i].to){
        int j=e[i].x;
        if(j==fa)continue;
        ddfs(j,x);
        vis[x]|=vis[j];
    }
}
void find(int x,int fa){
    for(int i=h[x];i!=-1;i=e[i].to){
        int j=e[i].x;
        if(j==fa)continue;
        if(vis[x]&&vis[j])ve.push_back(i);
        find(j,x);
    }
}
const int INF=2e9;
int d1[5010],d2[5010],p1[5010],up[5010],ac[5010];
void get_center(int x,int fa){
    d1[x]=d2[x]=-INF;
    st[x]=1;
    ac[x]=1;
    for(int i=h[x];i!=-1;i=e[i].to){
        if(flag[i])continue;
        int j=e[i].x;
        if(j==fa)continue;
        get_center(j,x);
        ll len=d1[j]+e[i].val;
        if(len>d1[x]){
            d2[x]=d1[x];
            d1[x]=len;
            p1[x]=j;
        }
        else if(len>d2[x]){
            d2[x]=len;
        }
    }
    if(d1[x]==-INF)d1[x]=d2[x]=0;
}
void dfss(int x,int fa){
    for(int i=h[x];i!=-1;i=e[i].to){
        if(flag[i])continue;
        int j=e[i].x;
        if(j==fa)continue;
        if(p1[x]==j)up[j]=max(up[x],d2[x])+e[i].val;
        else up[j]=max(up[x],d1[x])+e[i].val;
        dfss(j,x);
    }
}
int solve(int val){
    memset(st,0,sizeof st);
    int flag1=1;
    int mi1=2e9,mi2=2e9,l1,l2;
    int pos1=-1,pos2=-1;
    for(int i=1;i<=n;i++){
        if(!st[i]){
            memset(up,0,sizeof up);
            memset(p1,0,sizeof p1);
            ans=-1;
            dfs(i,-1,0);
            ans=-1;
            dfs(pos,-1,0);
            get_center(i,-1);
            dfss(i,-1);
            if(flag1){
                for(int i=1;i<=n;i++){
                    if(ac[i]){
                        mi1=min(mi1,max(d1[i],up[i]));//距离中心最远点的距离
                    }
                }
                l1=ans;//存储连通块的直径的长度
                memset(ac,0,sizeof ac);
            }
            else{
                for(int i=1;i<=n;i++){
                    if(ac[i]){
                        mi2=min(mi2,max(d1[i],up[i]));//距离中心最远点的距离
                    }
                }
                l2=ans;//存储连通块的直径的长度
                memset(ac,0,sizeof ac);
            }
            flag1=0;
        }
    }
    return max(l1,max(l2,mi1+mi2+val));
}
int main(){
    memset(h,-1,sizeof h);
    scanf("%d",&n);
    int a,b,c;
    for(int i=1;i<n;i++){
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c),add(b,a,c);
    }
    ans=-1;
    dfs(1,-1,0);//找最远点
    int pp=pos;
    ans=-1;
    dfs(pos,-1,0);//找直径的另一个点
    int res=1e9;
    vis[pp]=1;
    ddfs(pos,-1);//标记直径上的点
    find(pos,-1);//存储直径上的边
    for(int i=0;i<ve.size();i++){
        flag[ve[i]]=1;//标记直径上的边
        flag[ve[i]^1]=1;//标记直径上的边
        if(i!=0)flag[ve[i-1]]=0,flag[ve[i-1]^1]=0;//还原上一次标的边
        int f=solve(e[ve[i]].val);//求答案
        res=min(f,res);
    }
    printf("%d\n",res);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值