Atcoder Grand Contest 018 D Tree and Hamilton Path

Tree and Hamilton Path

Problem Statement

给出一棵有 n 个点的带边权的树,边权≤108,最大化 n1i=1dis(pi,pi+1) ,其中 p n的一个排列。

Data Constraint

n <=105

Solution

一个显然的结论,一条边( a ,b)会被计算的上限为 2 *min( n -sizeb, sizeb )。

先找出树的重心,接下来分类讨论。
若只有一个重心,也就意味着重心的每棵子树的大小严格小于 n2 ,那么设 p1 为重心,上限就一 定可以跑满(若干棵子树间跳来跳去,每次从一棵中跳到另一棵),但我们可以发现,唯独重心连向各棵子树的边中的其中一条边会比上限少 1 (因为pn不能设为重心),找到这些边中边权最小的一条减去它多余的的贡献即可(对应的那个点即为 pn )。

若有两个重心,则这两个重心一定连在一起,并且若把连接它们的边断开可以分成两棵大小严格为 n2 的树。接着我们可以发现,连着两个重心的边最多只能跑上限- 1 <script type="math/tex" id="MathJax-Element-464">1</script>次,而其他边都能跑满上限,统计答案即可。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

#define fo(i,j,l) for(int i=j;i<=l;++i)
#define fd(i,j,l) for(int i=j;i>=l;--i)

using namespace std;
typedef long long ll;
const ll N=12e4,M=2*N;

int fa[N],ne[M],la[N],lb[M],size[N],zd[N];
ll len[M],ans=0;
int oo,n;
ll ok=0;

inline void llb(int a,int b,ll z)
{ne[++oo]=la[a]; la[a]=oo; lb[oo]=b; len[oo]=z;};

inline int min(int a,int b)
{return a<b?a:b;}

inline int max(int a,int b)
{return a>b?a:b;}

inline ll min(ll a,ll b)
{return a<b?a:b;}

inline ll readll()
{
    ll o=0; char ch=' ';
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';ch=getchar())o=o*10+ch-48;
    return o;
}

inline int read()
{
    int o=0; char ch=' ';
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';ch=getchar())o=o*10+ch-48;
    return o;
}

void dg(int o)
{
    size[o]=1;
    for(int y=la[o];y;y=ne[y])
    if(!fa[lb[y]]){
        fa[lb[y]]=o;
        dg(lb[y]);
        size[o]+=size[lb[y]];
        if(size[lb[y]]*2==n)ok=len[y];
        ans=ans+len[y]*min(size[lb[y]],n-size[lb[y]]);
    }
    zd[o]=n-size[o];
    for(int y=la[o];y;y=ne[y])
    if(fa[lb[y]]==o)zd[o]=max(zd[o],size[lb[y]]);
}

int main()
{
    cin>>n;
    fo(i,1,n-1){
        int a=read(),b=read();
        ll Z=readll();
        llb(a,b,Z); llb(b,a,Z);
    }
    fa[1]=-1; dg(1);
    if(ok==0){
        int o=0; zd[0]=n+1;
        fo(i,1,n)if(zd[i]<zd[o])o=i;
        ok=(ll)12e17;
        for(int y=la[o];y;y=ne[y])
        ok=min(ok,len[y]);
    }
    ans=(ans<<1)-ok;
    cout<<ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值