Codeforces Round #294 (Div.2) E Shaass the Great

博客探讨了在强连通树形结构中,删除一条边并以相同权值建立新边以最小化任意两点间距离之和的问题。通过枚举边,将问题分解为左右两部分的距离和,并找到每部分的中心节点以优化计算。总结了求解过程中关键的数学模型和优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:有一棵树,n个节点,n-1条边,每条边有权值,保证树是强连通的。

如果删掉一条边,在任意两个点之间建一条和该边权值相同的边(仍要保证是连通的,可以与原节点相同),这时任意两点间的距离之和为sum。

问sum最小为多少。


枚举任意一条边,把该点删掉以后再新建一条边,假设把树分为左右两堆,可以发现sum分为三个部分。

1. 左边任意两点的距离和。

2. 右边任意两点的距离和。

3. 左边任一点到右边任意点的距离和。


其中,第三部分仍可以拆分。

假设左边的节点数为numl,  右边节点数为numr;

枚举的这条边在左边的端点为 pointl, 在右边的端点为 pointr.

左边的任一点到pointl 的距离和为 suml,  右边的任一点到 pointr 的距离和为sumr.


可以看到,从左边选一点, 如果该点想到选定右端点需要经历三个部分,

1.  左端点 -> pointl 

2. pointl -> pointr

3. pointr -> 右端点

如果是想到达所有的右端点, 这三部分的总和为

1.   ( 左端点 -> pointl  )  * numr  

2.  ( pointl -> pointr  )  *  (  numl * numr   )

3.   sumr


反之,从右到左也是一样的。

再经过简化 ,  每枚举一条边 

sum  =  suml * numr  + sumr * numl + c * (  numl * numr  )  + 左边任意两点距离和 + 右边两点距离和


可以看到, 在删除一条边 并 建立新边的过程中  

唯一变化的就只有 suml 和 sumr.

所以,每枚举一条边,  就只用求出最小的 suml  和 sumr  就行了   (  或者求出最大的变化量,用原图的 sum 减去最大的变化量  ,我是这么写的)

而suml 与 sumr 的确定与 pointl 和 pointr 有关

pointl 应该为 左子树的中心  ( 一棵树如果去掉一个点,使剩下的节点数最大的连通分量 的节点数最小, 则该店为中心 )

至于为什么,  可以参考类似  一维选址问题  来证明。


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;


long long map[5010][5010];  //任意两点间距离
long long temp;  //原图中任意两点距离总和
int head[5010];
int num;
struct
{
    int start,end;
    int next;
    int maxson;      //删去边后,以当前节点的儿子节点为根的子树中,节点数最大的子树的节点数
    long long dis;
}node[5010];
int f[5010];
int son[5010];
struct
{
    int to;
    int next;
    int c;
}edge[100010];

struct
{
    int x,y,c;
}a[5010];

void solve(int pre,int pos)  //求树形图上任意两点间的距离
{
    node[pos].start=node[pos].end=pos;
    node[pos].dis=0;
    for(int p=head[pos];p!=-1;p=edge[p].next)
    {
        if(edge[p].to!=pre)
        {
            solve(pos,edge[p].to);
            for(int i=node[edge[p].to].start;i!=-1;i=node[i].next)   node[i].dis+=edge[p].c;

            for(int i=node[pos].start;i!=-1;i=node[i].next)
                for(int j=node[edge[p].to].start;j!=-1;j=node[j].next)
                {
                    map[i][j]=map[j][i]=node[i].dis+node[j].dis;
                    temp+=map[i][j];
                }
            node[ node[pos].end ].next=node[ edge[p].to ].start;
            node[ pos ].end=node[edge[p].to].end;
        }
    }
}


int ip;
void addedge(int u,int v,int c)
{
    edge[ip].to=v;
    edge[ip].c=c;
    edge[ip].next=head[u];
    head[u]=ip++;
}

int dfs(int pre,int pos)
{
    f[num++]=pos;
    son[pos]=1;
    node[pos].maxson=0;
    for(int p=head[pos];p!=-1;p=edge[p].next)
    {
        if(edge[p].to!=pre)
        {
           son[pos]+=dfs(pos,edge[p].to);
           node[pos].maxson=max( node[pos].maxson,  son[ edge[p].to ]);
        }
    }
    return son[pos];
}
int main()
{int n;
    while(cin>>n)
    {
        memset(node,-1,sizeof(node));
        memset(head,-1,sizeof(head));
        ip=0;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].c);
            addedge(a[i].x,a[i].y,a[i].c);
            addedge(a[i].y,a[i].x,a[i].c);
        }
        temp=0;
        solve(0,1);

        long long ans=0;
        long long temp1,temp2;
        int toptemp;
        int numtemp;
        int aa,tt;
        int num1=0,num2=0;
        for(int i=1;i<n;i++)
        {
            num=0;
            temp1=temp2=0;
            aa=tt=1e9;
            numtemp =  dfs(a[i].x,a[i].y);
            numtemp =  (numtemp+1)/2;
            for(int j=0;j<num;j++)  //求树的中心
            {
                tt=max( node[ f[j] ].maxson , son[ a[i].y ] - son[ f[j] ] );  //去掉该点后节点数最大的连通分量的节点数
                if( tt<aa )
                {
                    aa=tt;
                    toptemp=f[j];
                }
            }

            for(int j=0;j<num;j++)  temp1+= map[ f[j] ][ a[i].y ] - map[ f[j] ][ toptemp ];

            num=0;
            numtemp =  dfs(a[i].y,a[i].x);
            numtemp =  (numtemp+1)/2;
            aa=tt=1e9;
            for(int j=0;j<num;j++)  //求树的中心
            {
                tt=max( node[ f[j] ].maxson , son[ a[i].x ] - son[ f[j] ] );  
                if( tt<aa )
                {
                    aa=tt;
                    toptemp=f[j];
                }
            }
            for(int j=0;j<num;j++)  temp2+= map[ f[j] ][ a[i].x ] - map[ f[j] ][ toptemp ];

            ans=max(ans,  temp1 * son[ a[i].x ] + temp2 * son[ a[i].y ]  );
        }
        cout<<temp-ans<<endl;

    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值