zoukankan      html  css  js  c++  java
  • P1364 医院设置 题解

    CSDN同步

    原题链接

    简要题意:

    求带权树的重心。

    带权树的重心定义:用 (dis_{x,y}) 表示 (x)(y) 的距离((x=y)(dis_{x,y} = 0)),即求一个节点 (u),最小化:

    [sum_{i=1}^n w_i imes dis_{i,u} ]

    本题要求求这个最小值。

    首先,按照题目要求建树,分算法讨论。

    算法一

    (1 leq n leq 100)

    显然,我们可以从每个点开始搜索,用 (O(n^2)) 的时间求出 ( ext{dis}) 数组。

    然后,再枚举重心取最小值即可完成答案。

    时间复杂度:(O(n^2)).

    期望得分:(100pts).

    算法二

    注意到,如果本题加强为:

    (1 leq n leq 10^6)

    那么我们需要一些高效算法。

    这里我们考虑 换根 ( ext{dp}).

    即我们先算出 (u = 1) 的答案 (f_u),然后对于一组父子关系 (u , v)(u)(v) 的父亲),那么 (f_u)(f_v) 有什么关系呢?

    我们只需要考虑两部分:

    • 原来在 (v) 的子树中的数,离 (v)(u)(1),所以答案集体 (-1).

    • 原来不在 (v) 子树中的数,离 (v)(u)(1),所以答案集体 (+1).

    综上可得:

    [f_v = f_u + ( siz_1 - siz_v ) - siz_v ]

    其中 (siz_i)(i) 的子树权值和。 (siz_1) 即全树权值和((1) 为根),(siz_1 - siz_v) 为不在 (v) 子树中的 (+1),在的 (-1)( herefore - siz_v).

    有了这个 ( ext{dp}),我们只需要搜索初始化 (siz)(f_1) 就可以啦!

    时间复杂度:(O(n)).

    实际得分:(100pts).

    #pragma GCC optimize(2)
    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=2e5+1;
    
    inline int read(){char ch=getchar();int f=1; while(!isdigit(ch)) {if(ch=='-') f=-f; ch=getchar();}
    	   int x=0;while(isdigit(ch)) x=x*10+ch-'0',ch=getchar(); return x*f;}
    
    int n,ans,w[N],siz[N],f[N];
    vector<int> G[N];
    
    inline void dfs(int u,int fa,int dep) {
    //当前节点为 u , 父亲为 fa , 深度为 dep
    	siz[u]=w[u]; //先处理自己
    	for(int i=0;i<G[u].size();i++) {
    		int v=G[u][i]; if(v==fa) continue;
    		dfs(v,u,dep+1); siz[u]+=siz[v]; //统计儿子节点
    	} f[1]+=w[u]*dep; //记录 f[1] 的答案
    }
    
    inline void dp(int u,int fa) {
    	for(int i=0;i<G[u].size();i++) {
    		int v=G[u][i]; if(v==fa) continue;
    		f[v]=f[u]+siz[1]-(siz[v]<<1); // <<1 等价于 *2
    		dp(v,u); //换根 dp
    	} ans=min(ans,f[u]); //统计答案
    }
    
    int main(){
    	n=read(); ans=INT_MAX;
    	for(int i=1;i<=n;i++) {
    		w[i]=read(); int u=read(),v=read();
    		if(v) G[i].push_back(v),G[v].push_back(i);
    		if(u) G[i].push_back(u),G[u].push_back(i); //建树
    	} dfs(1,0,0); dp(1,0); //初始化,然后换根 dp
    //	for(int i=1;i<=n;i++) printf("%d ",siz[i]); puts(""); 
    //	for(int i=1;i<=n;i++) printf("%d ",f[i]); puts("");
    	printf("%d
    ",ans);
    	return 0;
    }
    
    
    
  • 相关阅读:
    通过hmc启动lpar的终端
    修复LVM手记
    通过VMLibrary在client partition上安装AIX全程实录
    【转】通过VIOS实现AIX系统的网络虚拟化
    rhel 6 启动流程分析(/etc/inittab)
    Linux中tty、pty、pts的概念区别
    Shell中while循环的done 后接一个重定向<
    搭建dns服务器时报错error: bind: address already in use
    关于 smit mktcpip 和smit chinet 的区别
    博客园博客停更(本博客收集本人于2018年之前的博客,2018年之后的博客统一发布在CSDN上)
  • 原文地址:https://www.cnblogs.com/bifanwen/p/12651271.html
Copyright © 2011-2022 走看看