【题目描述】
2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地。起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状结构。如果基地A到基地B至少要经过d条道路的话,我们称基地A到基地B的距离为d。
由于火星上非常干燥,经常引发火灾,人类决定在火星上修建若干个消防局。消防局只能修建在基地里,每个消防局有能力扑灭与它距离不超过2的基地的火灾。
你的任务是计算至少要修建多少个消防局才能够确保火星上所有的基地在发生火灾时,消防队有能力及时扑灭火灾。
【输入格式】
输入文件的第一行为n (n<=1000),表示火星上基地的数目。接下来的n-1行每行有一个正整数,其中文件第i行的正整数为a[i],表示从编号为i的基地到编号为a[i]的基地之间有一条道路,为了更加简洁的描述树状结构的基地群,有a[i]<i。
【输出格式】
输出文件仅有一个正整数,表示至少要设立多少个消防局才有能力及时扑灭任何基地发生的火灾。
S a m p l e    O u t p u t Sample\;Output SampleOutput
6
1
2
3
4
5
S a m p l e    O u t p u t Sample\;Output SampleOutput
2
【题意分析】
简化后的题意:给你一棵树,在树上取一些点,所有和这些点树上距离小于等于2的点将会被覆盖。求覆盖整棵树最少需要取几个点。
树形dp太麻烦。。。方程不好推。。。经过分析后贪心是可行的:
对于一个没有被覆盖的点,任何情况下选择它的祖父是最优的。
从底部往上推,如果当前点没有被覆盖,那么就选取它的祖父。不用判断祖父是否被覆盖,因为是从下往上推的。那么就选取它的祖父,暴力扩展就可以了。
何为从底往上推?就是以深度为关键字维护一个大根堆,每次取出堆顶。
//大根堆写挂了调了10min,rp-=INF
Code:
type Front_Link_Star = record
tar,next : longint;
end;
const MAXN = 5000;
var edge : array [-10..MAXN*2] of Front_Link_Star;
head : array [-10..MAXN*2] of longint;
depth,Heap,father : array [-10..MAXN] of longint;
vis : array [-10..MAXN] of boolean;
n,cnt,size,x,i,ans : longint;
//前向星连边
Procedure connect (x,y : longint);
begin
inc (cnt);
edge[cnt].tar := y;
edge[cnt].next := head[x];
head[x] := cnt;
end;
Procedure swap (var x,y : longint);
var temp : longint;
begin
temp := x; x := y; y := temp;
end;
//加入大根堆
Procedure Heap_Insert (x : longint);
var pos : longint;
begin
inc (size); Heap[size] := x; pos := size;
while pos > 0 do
begin
if (depth[Heap[pos]] > depth[Heap[pos >> 1]])
//
and (pos >> 1 <> 0)
//就是这一句,不加的话会自动跟0比较,然后傻傻地交换。。。
then swap (Heap[pos],Heap[pos >> 1])
else break;
pos := pos >> 1;
end;
end;
//删除堆顶元素
Procedure Heap_Delete ();
var pos,target : longint;
begin
swap (Heap[1],Heap[size]); dec (size); pos := 1;
while (pos << 1) <= size do
begin
target := pos << 1;
if (depth[Heap[target]] < depth[Heap[target or 1]])
and (target or 1 <= size)
then target := target or 1;
if depth[Heap[pos]] < depth[Heap[target]]
then swap (Heap[pos],Heap[target])
else break;
pos := target;
end;
end;
//预处理出深度、父亲
Procedure DFS (now,fa,d : longint);
var i,v : longint;
begin
father[now] := fa; depth[now] := d;
i := head[now];
while i <> 0 do
begin
v := edge[i].tar;
if v = fa
then begin
i := edge[i].next;
continue;
end;
DFS (v,now,d+1); i := edge[i].next;
end;
end;
//扩张,d代表的是离源点的长度
//因为消防局势力范围是2,所以如果长度大于2就无用了
Procedure spread (now,d : longint);
var i,v : longint;
begin
if (d > 2) then exit;
i := head[now]; vis[now] := true;
while i <> 0 do
begin
v := edge[i].tar;
spread (v,d+1);
i := edge[i].next;
end;
end;
begin
readln (n);
//这里也是一个坑点,节点从1到n-1开始连
//因为我很懒所以就从2到n,省得+1
for i := 2 to n do
begin
readln (x);
connect (i,x);
connect (x,i);
end;
DFS (1,0,1);
for i := 1 to n do Heap_Insert (i);//放进堆里
while size > 0 do//堆不为空
begin
//如果堆顶已经被覆盖了,而且堆不为空,那么就取出来
while (vis[Heap[1]]) and (size > 0) do Heap_Delete ()
if size = 0 then break;
//取出祖父,进行扩张。
//如果祖父跳到根节点外面了,就默认是根节点。
if father[father[Heap[1]]] <> 0
then spread (father[father[Heap[1]]],0)
else spread (1,0);
inc (ans);//消防局个数++
end;
writeln (ans);
end.