[HNOI2003]消防局的设立

4 篇文章 0 订阅
2 篇文章 0 订阅

前言

原题戳这里

题目描述

2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地。起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状结构。如果基地A到基地B至少要经过d条道路的话,我们称基地A到基地B的距离为d。

由于火星上非常干燥,经常引发火灾,人类决定在火星上修建若干个消防局。消防局只能修建在基地里,每个消防局有能力扑灭与它距离不超过2的基地的火灾。

你的任务是计算至少要修建多少个消防局才能够确保火星上所有的基地在发生火灾时,消防队有能力及时扑灭火灾。

输入输出格式

输入格式:

输入文件名为input.txt。

输入文件的第一行为n (n<=1000),表示火星上基地的数目。接下来的n-1行每行有一个正整数,其中文件第i行的正整数为a[i],表示从编号为i的基地到编号为a[i]的基地之间有一条道路,为了更加简洁的描述树状结构的基地群,有a[i]

输出格式:

输出文件名为output.txt

输出文件仅有一个正整数,表示至少要设立多少个消防局才有能力及时扑灭任何基地发生的火灾。

输入输出样例

输入样例#1:

6
1
2
3
4
5

输出样例#2:

2

解法

首先又发现这是一道比较思维的题必须好好研究题目本身性质。
最简单的。我们发现叶子节点是最特殊的。如果一个叶子点要被覆盖,那么他的父亲或者兄弟或者爷爷有消防站。其中如果你是枚举一个一个还没有被消防站覆盖的点,那么最好建在他的爷爷或兄弟。因为父亲与他的距离为1.当然也不是说不能建在父亲,因为建在任何一处都有优点与缺点。
但是有一种情况是一定建在爷爷的。如果这个叶子节点没有兄弟或者兄弟没有儿子,那么建在父亲处肯定不优了,建在兄弟(如果有)处也不优了,只能建在爷爷处。可以证明,这些点是他们所在子树内深度较大的点,特殊的是局部深度最大的点。
由于这样的点一旦存在,就相当于把整个树缩小了好几圈。他们的父亲,兄弟(如果有),父亲的兄弟,父亲兄弟的儿子,爷爷的爷爷,爷爷的兄弟,爷爷的父亲,爷爷的兄弟都不需要考虑了。相当与剪掉了以爷爷为根的子树。
但显然这样做对于爷爷的兄弟的儿子没有影响换句话说,如果爷爷的兄弟没有儿子,那么爷爷的父亲与兄弟都不需要管了。但是这种情况下作为一个没有儿子的点,爷爷的兄弟在某一局部也能看成最深的点,这样势必就会考虑,然而实际上也许不需要考虑。

这就使我们不得不考虑如果真按这个思路去做,还要考虑这些局部最深的点之间相互的影响。那么显而易见的最深的点一定是需要考虑的。次深的点如果最深的点对它没有影响也是要考虑的。

上述所说都是针对叶子节点。如果推广到所有节点,那么就会存在一些不是叶子节点深度却深于叶子节点的点。但这不影响,因为他们的子孙已经被考虑过了(深度肯定非常深)而他们又不能被之前所建的消防站覆盖,那么他们的子孙对他没有任何贡献与影响,他们也已经相当与一个叶子节点了。

思路到了这里这道题已经分析的异常透彻了。越是透彻的分析,越能接近问题的本质。这题的本质就是贪心。在按深度的排序下,逐一考虑每一个点,最后得到的一定是最优解。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
template<class T>
inline void read(T&data){
    register char ch=0;data=0;
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch<='9'&&ch>='0'){
        data=(data<<3)+(data<<1)+(ch^48);
        ch=getchar();
    }
}
const int _ =1001;
int head[_],n,fa[_];bool pd[_];
struct edge{
    int nt,to;
}e[_<<1];
int ans,cnt,ans1;
struct node{
    int u,dep;
    bool operator < (const node a)const {
        return dep<a.dep;
    }
};
priority_queue<node> Q;
void dfs(register int now,register int ff,register int dep){
    fa[now]=ff;
    Q.push((node){now,dep});
    for(register int i=head[now];i;i=e[i].nt)
    {
        if(e[i].to==ff)continue;

        dfs(e[i].to,now,dep+1);
    }
}
void add(register int a,register int b){
    e[++cnt].to=b,e[cnt].nt=head[a],head[a]=cnt;
}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("1.out","w",stdout);
    read(n);
    for(register int i=2;i<=n;++i){
        register int to;read(to);
        add(i,to);add(to,i);
    }

    dfs(1,0,1);fa[1]=1;
    while(!Q.empty()){
        register node k=Q.top();
        Q.pop();
        if(pd[k.u])continue;
        pd[ fa[ fa[ k.u ] ] ]=1;
        register int now=fa[fa[k.u]];
        for(register int i=head[now];i;i=e[i].nt){
            pd[e[i].to]=1;
            for(register int j=head[e[i].to];j;j=e[j].nt){
                pd[e[j].to]=1;
            }
        }
        ans++;
    }
    cout<<ans;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值