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