noip2003 传染病防治

21 篇文章 0 订阅
11 篇文章 0 订阅

题目是给出一棵树(以1为根),每一次只在当前一层删掉一条边,每次病毒会扩散一层,求最小扩散到的节点数。

一开始想到贪心,删去儿子的子节点个数最小的点和根的连边,利用深搜。

但这样只有70分。。。

procedure dfs(x:longint);
var flag:boolean;
    max:longint;
i,j,k:longint;
begin
 flag:=false;//标记有没有可找的,看传染是否结束
 max:=-1;
 for i:=1 to son[x] do //贪心,找儿子中儿子个数最大的删去
   if son[c[x,i]]>max then 
    begin
max:=son[c[x,i]];
j:=c[x,i];
end;
 son[j]:=0;
 if son[x]>0 then ans:=ans+son[x]-1; 
 for i:=1 to son[x] do//把要删去的子节点的兄弟的儿子变为它的子节点
  if c[x,i]<>j then 
    for k:=1 to son[c[x,i]] do
begin 
 son[j]:=son[j]+1;
 c[j,son[j]]:=c[c[x,i],k];
 flag:=true;
end;
 if flag then dfs(j);
 if flag=false then 
  begin 
   if ans<min then min:=ans;
   exit;
 end;
end;

问题出在可能子节点最多的那个儿子后面的情况并不优,具有后效性。

从这题程序学到的:①给出边的关系,还要自己递归建树,因为给的边不一定有顺序,很容易挂。

                                    ②像这种树的关系判断是否到达最后一层,可以用flag标记,如果当前节点没有儿子,就可以比较最优解了。


var n,p,i,x,y,s,ans:longint;
    son,deep:array[1..300]of longint;
	c,a:array[1..300,1..300]of longint;
	f:array[1..300]of boolean;
procedure init;
begin
 read(n,p);
 for i:=1 to p do
  begin
   read(x,y);
   a[x,y]:=1;a[y,x]:=1;
  end;
end;
procedure buildtree(x:longint);
var i:longint;
begin
 f[x]:=false;
 for i:=1 to n do
  if (a[x,i]=1)and(f[i]) then 
   begin
    son[x]:=son[x]+1;
	c[x,son[x]]:=i;
	buildtree(i);
   end;
end;
procedure dfs(x:longint);//每次搜索一层,回溯的时候要恢复为没有搜过的
var flag:boolean;
    i,j,tmp:longint;
begin
 if s>ans then exit;//剪枝
 flag:=false;
 tmp:=s;//用于恢复
 for i:=1 to n do
  if deep[i]=x then //按层搜索,可以每次循环找一层的枚举
   for j:=1 to son[i] do
    begin
	 deep[c[i,j]]:=x+1;
	 flag:=true;
	 s:=s+1;//其他儿子
	end;
 if flag=false then//搜到的这层都没有点了,表示一种可能遍历完成 
  begin 
   if ans>s then ans:=s;
   exit;
  end;
 s:=s-1;//要删的点
 for i:=1 to n do 
  if deep[i]=x+1 then 
   begin 
    deep[i]:=0;//删这个点
    dfs(x+1);
	deep[i]:=x+1;
   end;
 s:=tmp;
 for i:=1 to n do if deep[i]=x+1 then deep[i]:=0;//每一层的恢复
end;
begin
 init;
 fillchar(f,sizeof(f),true);
 fillchar(s,sizeof(s),0);
 fillchar(c,sizeof(c),0);
 buildtree(1);
 deep[1]:=1;
 ans:=maxlongint;
 s:=1;
 dfs(1);
 writeln(ans);
end.



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值