题目是给出一棵树(以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.