树的双中心

 

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;
}

 

  

 

 

 

  

 

  

 

posted @ 2024-01-18 21:15  我微笑不代表我快乐  阅读(8)  评论(0)    收藏  举报