题目大意:有一棵树,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;
}