• 题解-SHOI2005 树的双中心


    SHOI2005 树的双中心

    给树 (T=(V,E)(|V|=n)),树高为 (h)(w_u(uin V))。求 (xin V,yin V:left(sum_{uin V}w_ucdot min(dis_{u,x},dis_{u,y}) ight)_{min})

    数据范围:(1le nle 50000)(1le hle 100)


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

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


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

    洛谷P1364 医院设置

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

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

    所以 (f_1=sum_{uin V}w_ucdot (dep_u-dep_1))

    [vin son_u:f_v=f_u+(sz_1-sz_v)-sz_v ]

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

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


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

    (g_i=sum_{uin subtree_i}w_ucdot dis_{u,i}),很明显上文的 (f_1=g_1)

    [ herefore g_u=sum_{vin 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 pin ancestor_b:sz_p-=sz_b)

    (now_1=sum_{uin V_1}w_ucdot dis_{u,1}=g_1-g_b-sz_b(dep_b-dep_1),now_2=sum_{uin V_2}w_ucdot dis_{u,b}=g_b)

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

    再看看上面的式子:

    [vin 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
    ",ans);
    	return 0;
    } 
    

    祝大家学习愉快!

  • 相关阅读:
    【福利】JetBrains 全家桶永久免费使用
    openlayers操作分享:如何从容的在vue中食用openlayers6
    Axios的正确食用方法
    uniapp 地图全解析+事件监听
    JS数据扁平化
    Codeforces 记录
    WP开发加载图片及文字Resource/Content说明
    根据属性获取属性名
    string和byte 数组转换的快速方法(采用unsafe)
    Code First Migrations更新数据库结构(数据迁移)
  • 原文地址:https://www.cnblogs.com/Wendigo/p/13043587.html
Copyright © 2020-2023  润新知