Loading

题解-SHOI2005 树的双中心

SHOI2005 树的双中心

给树 \(T=(V,E)(|V|=n)\),树高为 \(h\)\(w_u(u\in V)\)。求 \(x\in V,y\in V:\left(\sum_{u\in V}w_u\cdot \min(dis_{u,x},dis_{u,y})\right)_{\min}\)

数据范围:\(1\le n\le 50000\)\(1\le h\le 100\)


一眼思路:把 \(T\) 由一条边砍成 \(T_1,T_2\)\(x\)\(T_1\) 重心,\(y\)\(T_2\) 重心。

所以可以暴力枚举那条断边,然后找两棵树重心,合并答案。


先问个问题:一棵带点权的树怎么找重心?

洛谷P1364 医院设置

带点权树的重心 \(x\) 满足 \(f_x=\sum_{u\in V}w_u\cdot dis_{u,x}\) 最小。

暂定 \(1\) 为根,记录 \(sz_i\) 表示节点 \(i\) 的子树的权值 \(w\) 和。

所以 \(f_1=\sum_{u\in V}w_u\cdot (dep_u-dep_1)\)

\[v\in son_u:f_v=f_u+(sz_1-sz_v)-sz_v \]

这是换根 \(\tt dp\)。“重心”往下挪,上面的节点要多走一步,下面的节点少走一步。

然后 \(f_i\) 最小的 \(i\) 就是重心。


这题也用到了类似的思想:

\(g_i=\sum_{u\in subtree_i}w_u\cdot dis_{u,i}\),很明显上文的 \(f_1=g_1\)

\[\therefore g_u=\sum_{v\in son_u}g_v+sz_v \]

很明显吧,每个子节点答案加再走一条边的贡献。

\(sf_i\)\(i\)子树最大子节点\(sc_i\)\(i\)子树次大子节点

设断边为 \((a,b)\),其中 \(dep_b>dep_a\)

同样令 \(1\) 为根,同样维护 \(sz_i\)

所以 \(T_1\) 的根为 \(1\)\(T_2\) 的根为 \(b\)

\(\Theta(h)\)\(sz_i\) 变为 \(T_1,T_2\) 内的子树权值和:

\(\forall p\in ancestor_b:sz_p-=sz_b\)

\(now_1=\sum_{u\in V_1}w_u\cdot dis_{u,1}=g_1-g_b-sz_b(dep_b-dep_1),now_2=\sum_{u\in V_2}w_u\cdot dis_{u,b}=g_b\)

\(T_1,T_2\) 中的节点 \(i\) 子树最大子节点必为原 \(sf_i,sc_i\) 中的一个。

再看看上面的式子:

\[v\in son_u:f_v=f_u+(sz_{rt}-sz_v)-sz_v \]

所以 \(f_v<f_u\)\(sz_{rt}<2sz_v\)

可以从各自的根节点出发,摸着最大子树子节点\(T_1,T_2\) 的重心,答案可以由上面的 \(now\) 递推。


时间复杂度 \(\Theta(nh)\)


  • 代码

上面的内容看不懂就算了,读读这代码吧。。。

#include <bits/stdc++.h>
using namespace std;

//Start
typedef long long ll;
typedef double db;
#define mp(a,b) make_pair(a,b)
#define x first
#define y second
#define b(a) a.begin()
#define e(a) a.end()
#define sz(a) int((a).size())
#define pb(a) push_back(a)
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;

//Data
const int N=5e4;
int n,w[N+7];
vector<int> e[N+7];

//TreeDP
int dep[N+7],sz[N+7],fa[N+7],f[N+7],sf[N+7],sc[N+7];
void Dfs1(int u){
	sz[u]=w[u],dep[u]=dep[fa[u]]+1;
	for(int&v:e[u])if(v!=fa[u]){
		fa[v]=u,Dfs1(v),sz[u]+=sz[v],f[u]+=f[v]+sz[v]; //f就是g
		if(sz[v]>sz[sf[u]]) sc[u]=sf[u],sf[u]=v;
		else if(sz[v]>sz[sc[u]]) sc[u]=v;
	}
}
int cut;
void Dfs2(int u,int now,int sm,int&res){
	res=min(res,now);
	int v=(sf[u]==cut||sz[sc[u]]>sz[sf[u]])?sc[u]:sf[u]; //v为u最大子树子节点
	if(v&&2*sz[v]>sm) Dfs2(v,now+sm-2*sz[v],sm,res);
}
void Dfs3(int u,int&res){
	for(int&v:e[u])if(v!=fa[u]){
		cut=v;
		int up=inf,down=inf;
		for(int p=u;p;p=fa[p]) sz[p]-=sz[v];
		Dfs2(1,f[1]-f[v]-(dep[v]-dep[1])*sz[v],sz[1],up);
		Dfs2(v,f[v],sz[v],down);
		res=min(res,up+down);
		for(int p=u;p;p=fa[p]) sz[p]+=sz[v]; //回溯
		Dfs3(v,res);
	}
}

//Main
int main(){
	scanf("%d",&n);
	for(int i=1,u,v;i<=n-1;i++) scanf("%d%d",&u,&v),e[u].pb(v),e[v].pb(u);
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	int ans=inf; Dfs1(1),Dfs3(1,ans),printf("%d\n",ans);
	return 0;
} 

祝大家学习愉快!

posted @ 2020-06-04 14:56  George1123  阅读(136)  评论(0编辑  收藏  举报