#(树形DP?贪心!)P2279 [HNOI2003]消防局的设立(提高+/省选-)

题目描述

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]<i。

输出格式

输出文件名为output.txt

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

输入输出样例

输入 #1复制
6
1
2
3
4
5
输出 #1复制
2
思考:(这是我做题时的想法,大部分与正解不同233)
想法1:
问题转化为:给你一颗树,并给出一个半径为2距离的容器,求最少的容器覆盖数满足整个图。
问题中需要考虑的状态:首先,考虑当前的节点,f[i][j]存储从当前的节点出发,j距离内的所有的节点个数
设s为i的儿子节点
f[i][j]=∑f[s][j-1]-(sonnum-1)*f[i][j-2];
这个可以求得每个节点j范围内的节点个数(详细见lg3047附近的牛)
然鹅求出来又有什么用?
想法2:
需要我们维护f[i][1],f[i][0]两个状态,分别维护第i个节点不放置,和第i个节点放置后的最大有用覆盖面积?
np。。。。。

就是找最低没被覆盖到的点,并在它的祖父处设一个消防站。考虑到这个点的所有子孙后代都已经被覆盖了,因此这时覆盖祖父能盖到更多额外的点,并保证结果不会更差。

很多思路是用dfs或堆求取最低节点,实际上没必要,只要预处理出深度(边输入边处理)并排序,碰到已覆盖就跳过,未覆盖就在祖父处设消防站,ans++。

问题在于怎样才能判断这个点覆盖到了没有。对于儿子或孙子覆盖他,可以在在儿子处设站时就标记它;而对于父亲和祖父覆盖他,可以用儿子对父亲的映射f来解决;问题在于兄弟。其实,可以用o数组维护“离i最近的消防站到i的距离”,当o[父亲]==1时,就能确定它是否被覆盖。

AC代码:

#include<iostream>
#include<algorithm>
#define For(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
int n,b[1001],d[1001],o[1001],f[1001],ans;//b数组存储节点标号,d数组存储深度
bool cmp(int x,int y)//o存储到节点i的最近消防站距离,f数组存储i节点的父亲节点
{
return d[x]>d[y];//sort函数用于按深度将节点进行排序
}
inline int min(int x,int y)
{
return x<y?x:y;
}
int main()
{
cin>>n;
b[1]=1,o[1]=o[0]=n;//初始化根节点,1,0,节点距离最远!
For(i,2,n)
{
cin>>f[i];//输入i节点的父亲
d[i]=d[f[i]]+1;//更新深度,从父亲哪里继承
b[i]=i;//记录节点
o[i]=n;//初始化最远的消防站位置
}
sort(b+1,b+1+n,cmp);//按深度从大到小排序
For(i,1,n)
{//
int w=b[i],v=f[b[i]],u=f[f[b[i]]]; //获取节点w的父亲和爷爷
o[w]=min(min(o[v]+1,o[u]+2),o[w]);//更新节点w的o
if(o[w]>2)//若需要放置消防站
{//
o[u]=0;//则在爷爷节点处放置消防站
ans++;//计数
o[f[u]]=min(o[f[u]],o[u]+1);//通过爷爷节点覆盖更多节点
o[f[f[u]]]=min(o[f[f[u]]],o[u]+2);//太太爷爷节点
}

}
cout<<ans;
return 0;
}

转载于:https://www.cnblogs.com/little-cute-hjr/p/11430469.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值