zoukankan      html  css  js  c++  java
  • 洛谷P1364 医院设置(树形DP/二次扫描与换根法)

    题目描述

    设有一棵二叉树,如图:

    其中,圈中的数字表示结点中居民的人口。圈边上数字表示结点编号,现在要求在某个结点上建立一个医院,使所有居民所走的路程之和为最小,同时约定,相邻接点之间的距离为 111。如上图中,若医院建在1 处,则距离和 =4+12+2×20+2×40=136=4+12+2 imes20+2 imes40=136=4+12+2×20+2×40=136;若医院建在 333 处,则距离和 =4×2+13+20+40=81=4 imes2+13+20+40=81=4×2+13+20+40=81。

    输入格式

    第一行一个整数 nnn,表示树的结点数。

    接下来的 nnn 行每行描述了一个结点的状况,包含三个整数 w,u,vw, u, vw,u,v,其中 www 为居民人口数,uuu 为左链接(为 000 表示无链接),vvv 为右链接(为 000 表示无链接)。

    输出格式

    一个整数,表示最小距离和。

    输入输出样例

    输入 #1
    5						
    13 2 3
    4 0 0
    12 4 5
    20 0 0
    40 0 0
    
    输出 #1
    81
    首先注意到这个题的数据范围很小,n只有1e2,因此枚举每个节点进行DFS可以通过,复杂度为O(n^2)。
    考虑更高效的方法,受到题解区以及蓝书的启发,对于这种没有明显树根的题,可以采用二次扫描换根的方法,先DFS预处理,然后枚举每个点进行答案的更新。
    具体来说,DFS的时候要预处理这样一个数组:size[i]代表当以1这个节点为树根时(换根),以i节点为根的子树的节点数。注意,这里的节点数实际上是子树的节点的权值和!和普通的树的重心不一样。同时,DFS时还能求出以1为树根时的距离和,存到f[1]里。
    然后从1开始进行转移,设x的下一个节点是y,相较于x作为树根的路径和f[x],可以看到f[y]的值的变化情况:
    1.减少了size[y]*1,因为以y为根的子树的所有节点原来要走到x,现在可以少走一条边,总的就是减少了size[y].
    2.增加了(size[1]-size[y])*1,因为除去以y为根的子树的节点,还剩下size[1]-size[y]个节点(乘上权值),而这些节点需要多走一步。
    转移方程就可以写出来了f[y]=f[x]+(size[1]-size[y])*1-size[y]*1; 满足无后效性,而且能知道dp的“阶段”就是以1为树根的树的当前深度。在更新的时候对答案取min更新即可。
    #include <bits/stdc++.h>
    #define N 105
    using namespace std;
    int n,m,head[N],ver[2*N],Next[2*N],tot=0;
    int num[N],f[N],size[N],ans=0;//f[i]表示以i为根的总距离,size[i]表示以u为根的子树的大小 包括自己(对子树所有节点权值求和)。
    void add(int x,int y)
    {
        ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
    }
    void dfs(int x,int d,int pre)
    {
        int i;
        size[x]=num[x];
        for(i=head[x];i;i=Next[i])
        {
            int y=ver[i];
            if(y==pre)continue;
            dfs(y,d+1,x);
            size[x]+=size[y];
        }
        f[1]+=num[x]*(d-1);//逐层累加 注意是当前节点乘以深度减一累加上去 
    }
    void dp(int x,int pre)
    {
        int i;
        for(i=head[x];i;i=Next[i])
        {
            int y=ver[i];
            if(y==pre)continue;
            f[y]=f[x]+(size[1]-size[y])*1-size[y]*1; //转移 
            dp(y,x);
        }
        ans=min(ans,f[x]);
    }
    int main()
    {
        cin>>n;
        int i;
        for(i=1;i<=n;i++)
        {
            int w,u,v;
            scanf("%d%d%d",&w,&u,&v);
            num[i]=w;//节点的权值 
            if(u)add(i,u),add(u,i);//得存双向边 
            if(v)add(i,v),add(v,i);
        }
        dfs(1,1,0);
        ans=f[1];
        dp(1,0);
        cout<<ans;
        return 0;
    }



  • 相关阅读:
    mybatis generator插件开发
    webserver实现
    Linux的selinux
    oracle从备份归档日志的方法集中回收
    百度地图 Android SDK
    Oracle实践--PL/SQL表分区的基础
    js插件---jquery给表格添加行列
    m_Orchestrate learning system---三十四、使用重定义了$的插件的时候最容易出现的问题是什么
    2018十大国产佳片
    javascript对象使用总结
  • 原文地址:https://www.cnblogs.com/lipoicyclic/p/12708661.html
Copyright © 2011-2022 走看看