luogu P2726 [SHOI2005]树的双中心

传送门

强行安利->巨佬题解

如果只有一个点贡献答案,那么答案显然是这棵树的带权重心,这个是可以\(O(n)\)求的.一个\(O(n^2)\)暴力是枚举两个集合之间的分界边,然后对这两个集合分别算答案,合并更新

考虑优化此过程,一个结论是一棵树内,只有\(size_i*2>size_{root}\)的点才有可能成为带权重心,并且这一类点个数不超过2个 不会证啊qwq,感性理解一下吧.所以每次枚举是哪条边为分界线,然后把树分成两部分,从每个树的根开始算答案,如果\(size_i*2\le size_{root}\)就停止

注意要扣除下面子树对上面子树的\(size\)的贡献再做

更多细节详见代码

#include<bits/stdc++.h>
#define LL long long
#define il inline
#define re register
#define db double
#define eps (1e-5)

using namespace std;
const int N=50000+10;
il LL rd()
{
  LL x=0,w=1;char ch=0;
  while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
  while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
  return x*w;
}
int to[N<<1],nt[N<<1],hd[N],tot=1;
il void add(int x,int y)
{
  ++tot,to[tot]=y,nt[tot]=hd[x],hd[x]=tot;
  ++tot,to[tot]=x,nt[tot]=hd[y],hd[y]=tot;
}
int n,fa[N],sz[N],de[N],g[N],fc[N],sc[N],no,ans=1<<30;
void dfs(int x)
{
  for(int i=hd[x];i;i=nt[i])
	{
	  int y=to[i];
	  if(y==fa[x]) continue;
	  fa[y]=x,de[y]=de[x]+1,dfs(y),sz[x]+=sz[y],g[x]+=g[y]+sz[y];
	  if(sz[fc[x]]<=sz[y]) sc[x]=fc[x],fc[x]=y;
	  else if(sz[sc[x]]<sz[y]) sc[x]=y;
	}
}
void cal(int x,int nw,int size,int &an)
{
  an=min(an,nw);
  int y=sz[fc[x]]<sz[sc[x]]||fc[x]==no?sc[x]:fc[x];
  if(sz[y]*2>size) cal(y,nw-sz[y]+size-sz[y],size,an);
}
void work(int x)
{
  for(int i=hd[x];i;i=nt[i])
	{
	  int y=to[i];
	  if(y!=fa[x])
		{
		  no=y;
		  for(int xx=x;xx;xx=fa[xx]) sz[xx]-=sz[y];
		  int aa=1<<30,bb=1<<30;
		  cal(1,g[1]-g[y]-de[y]*sz[y],sz[1],aa),cal(y,g[y],sz[y],bb);
		  for(int xx=x;xx;xx=fa[xx]) sz[xx]+=sz[y];
		  ans=min(ans,aa+bb);
		  work(y);
		}
	}
}

int main()
{
  n=rd();
  for(int i=1;i<n;i++) add(rd(),rd());
  for(int i=1;i<=n;i++) sz[i]=rd();
  dfs(1),work(1);
  printf("%d\n",ans);
  return 0;
}


posted @ 2018-11-05 22:25  ✡smy✡  阅读(98)  评论(0编辑  收藏  举报