树的双中心
Description
给出一棵树,每个节点有个正的权值w[i],树的边权为1
记d[u,v]为u与v之间的距离,如果u与v重合,则值为0
现在希望找2个点x,y出来,使得下式值最小
s(x,y)=sigma(w(v)*min(d(v,x),min(v,y))
v为树中的所有点
Format
Input
第一行为N,1<N<=50000,表示树的节点数目,树的节点从1到N编号。
接下来N-1行,每行两个整数U,V,表示U与V之间有一条边。
再接下N行,每行一个正整数,其中第i行的正整数表示编号为i的节点权值为W(I)
树的深度<=100
Output
输出最小的s(x,y),结果保证不超过10^9
Samples
输入数据 1
5
1 2
1 3
3 4
3 5
5
7
6
5
4
输出数据 1
14
Hint
选取的两个中心节点分别为2和3
SOl
本题采用一个新的方式求树的重心 ,找出每个点的重儿子及次重儿子。
当发现一个点的重儿子v的大小,超过了总大小的一半,则重心在以v为根的子树中
不断的这样移动,直到不能移动为止。
为什么要求次重儿子,是因这个题涉及删除操作,删除后,某个点的重儿子及次重儿子会情况会发现变化,所以要记录次重儿子。
首先对整个树做次dfs求出相关的点的深度信息,子树大小信息,重儿子与次重儿子信息等
并算出没有删除前,所有点集合中到1号点的代价。
接下来枚举删除哪一条边
假设删除的是3--6这条边
则原树分裂成两个子树
以6为根的子树中各个点的最大子树与次大子树没有变化。
但对于3来说,6这个子树没有了,所以从3到1这条路径上所有点的子树点的个数要减少,减少的量为以6为根的子树大小。
于是就会引发3到根这条路径上所有点的最大子树与次大子树的变化。
于是算出来
然后开始求两个树的重心。
对于以1为根的子树,按上述所讲的方法,不断进行移动,并调整总代价
同理
以6为根的树,也是这样做。
因为是枚举删除哪一条边,所以做完后,还要再恢复这个树,尝试枚举下一条边。
分别对这两个子树重新求出新的代价出来即可
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N=50010; struct node { int x,y,next; } a[2*N]; int n,first[N],len,size[N],s1[N],s2[N],dep[N],w[N],fa[N]; void ins(int x,int y) { a[++len].x=x,a[len].y=y,a[len].next=first[x],first[x]=len; } void dfs(int x,int f) { size[x]+=w[x]; for(int k=first[x]; k; k=a[k].next) { int y=a[k].y; if(y==f)continue; dep[y]=dep[x]+1,fa[y]=x; dfs(y,x); size[x]+=size[y]; if(size[s1[x]]<size[y]) s2[x]=s1[x],s1[x]=y; else if(size[s2[x]]<size[y]) s2[x]=y; } } int main() { int i,k,x,y; scanf("%d",&n); len=0; for(i=1; i<n; i++) { scanf("%d%d",&x,&y); ins(x,y),ins(y,x); } for(i=1; i<=n; i++) scanf("%d",&w[i]); dfs(1,0); LL s=0; for(i=2; i<=n; i++) //假设1号点为树的重心,求出最开始的代价 s+=size[i]; LL mn=(1ll<<60); for(k=1; k<=len; k+=2) { x=a[k].x,y=a[k].y; if(dep[x]<dep[y]) swap(x,y); LL sum=s; int u,v; for(u=fa[x]; u; u=fa[u]) //将x这个子树,从它的父亲点,从它的爷爷点的树中分出去 //并且最开始的代价也要不断的减去 size[u]-=size[x],sum-=size[x]; u=1; while(1) { if(size[s1[u]]>size[s2[u]] && s1[u]!=x) v=s1[u]; else v=s2[u]; if(size[v]>size[1]/2) //如果v的这个子树大小,大于整个树的一半,则重心移到这个点 sum+=size[1]-2*size[v],u=v; else break; } u=x; //算出以x为根的子树,它们应该在哪个点集合 while(1) { v=s1[u]; //v为u的重儿子 if(size[v]>size[x]/2) //如果大于size[x]的一半,则移动到v点来 sum+=size[x]-2*size[v],u=v; else break; } mn=min(mn,sum); for(u=fa[x]; u; u=fa[u]) //还原到最开始的情况 size[u]+=size[x]; } printf("%lld\n",mn); return 0; }