[Poj]3659——树形DP

[题目大意]
  • 给定一棵树,在某一个点上放置一个基站需要花费1单位的代价,可以覆盖自己和相邻的点,求覆盖所有点的最小代价。

[分析题解]
  •  对于一个点来说,其能被覆盖只有三种方法:父亲放置基站,自己放置基站,某个儿子放置基站。这样的话可以想到枚举状态来动态规划。
  • 动态规划的状态表示方法是非常多的,我的表示比较繁琐,是这样的,先建立有根树,然后用F[I,J]表示以I为根的子树,状态为J的最小覆盖代价。
  • J=0代表I的父亲没有放,I不放置。这样,就要从I的儿子中找一个放基站,才能保证I被覆盖。其他的儿子随意。
  • J=1代表I的父亲放置了,I不放置。这样I的儿子可以随意,爱放不放,反正I已经被覆盖,儿子的事情那是子问题里需要解决的。
  • J=2代表I的父亲没有放,I放置了。既然I已经放置了,儿子也是随意放。但是代价要+1,因为在I处放置了一个。
  • J=3代表I的父亲放置了,I放置了。同上。其实2、3可以合并为一种情况,为了显得厉害一些(其实是脑子小),就拆成两个了。
  • 这样按照定义转移一下就好了,最后输出Min(F[Root,0],F[Root,2]);

[个人代码]
View Code
  1 //10082338      perseawe        3659    Accepted        1356K   79MS    Pascal  2528B   2012-04-16 22:41:13 
  2 
  3 Const
  4   MaxNode=10000+100;
  5   MaxEdge=MaxNode*2;
  6   Inf=1000000;
  7 
  8 Var
  9   n,tot:Longint;
 10   Flag:Array [0..MaxNode] of Boolean;
 11   hv,Line,lv,Father:Array [0..MaxNode] of Longint;
 12   F:Array [0..MaxNode,0..3of Longint;
 13   Edge:Array [0..MaxEdge] of Record next,wh:Longint;end;
 14 
 15 Function Min(a,b:Longint):Longint;begin if a<b then exit(a);exit(b);end;
 16 
 17 Procedure AddEdge(u,v:Longint);
 18   begin
 19     inc(tot);
 20     Edge[tot].next:=hv[u];hv[u]:=tot;
 21     Edge[tot].wh:=v;
 22     inc(tot);
 23     Edge[tot].next:=hv[v];hv[v]:=tot;
 24     Edge[tot].wh:=u;
 25   end;
 26 
 27 Procedure Init;
 28   var
 29     i,u,v:Longint;
 30   begin
 31     readln(n);
 32     for i:=1 to n-1 do
 33       begin
 34         readln(u,v);
 35         AddEdge(u,v);
 36       end;
 37   end;
 38 
 39 Procedure BFS_Lebal;
 40   var
 41     I,tnode,u,v,top,tail:Longint;
 42   begin
 43     For I:=N downto 1 do
 44       begin
 45         F[I,0]:=Inf;
 46         F[I,1]:=0;
 47         F[I,2]:=1;
 48         F[I,3]:=1;
 49       end;
 50     Fillchar(lv,sizeof(lv),0);Lv[1]:=1;
 51     top:=0;tail:=1;Line[1]:=1;
 52     Repeat
 53       inc(top);
 54       u:=Line[top];
 55       tnode:=hv[u];
 56       while tnode<>0 do
 57         begin
 58           v:=Edge[tnode].wh;
 59           if lv[v]=0 then
 60             begin
 61               F[u,0]:=0;
 62               lv[v]:=lv[u]+1;
 63               Father[v]:=u;
 64               inc(tail);
 65               Line[tail]:=v;
 66             end;
 67           tnode:=Edge[tnode].next;
 68         end;
 69     Until top=tail;
 70   end;
 71 
 72 Procedure DP;
 73   var
 74     I,u,v,Tmp,Tnode,Plus:Longint;
 75   begin
 76     Fillchar(Flag,Sizeof(Flag),False);
 77     For I:=N downto 1 do
 78       begin
 79         u:=Line[I];
 80         //UpDate F[u,0]
 81         if not(Flag[u]) then
 82           begin
 83             tnode:=hv[u];
 84             Tmp:=MaxLongint;Plus:=0;
 85             While tnode<>0 do
 86               begin
 87                 v:=Edge[tnode].wh;
 88                 if lv[u]+1=lv[v] then if F[v,2]<Tmp then begin Tmp:=F[v,2];Plus:=F[v,2]-F[v,0];end;
 89                 tnode:=Edge[tnode].next;
 90               end;
 91             Inc(F[u,0],Plus);
 92           end;
 93         //UpDate His Father
 94         Tmp:=Min(F[u,0],F[u,2]);
 95         Inc(F[Father[u],0],Tmp);
 96         Inc(F[Father[u],1],Tmp);
 97         If Tmp=F[u,2then Flag[Father[u]]:=True;
 98 
 99         Tmp:=Min(F[u,1],F[u,3]);
100         Inc(F[Father[u],2],Tmp);
101         Inc(F[Father[u],3],Tmp);
102       end;
103   end;
104 
105 Procedure Main;
106   begin
107     BFS_Lebal;
108     DP;
109   end;
110 
111 Procedure Print;
112   begin
113     writeln(Min(F[1,0],F[1,2]));
114   end;
115 
116 Begin
117   assign(INPUT,'tower.in');Reset(INPUT);
118   Init;
119   Close(INPUT);
120   Main;
121   assign(OUTPUT,'tower.out');Rewrite(OUTPUT);
122   Print;
123   Close(OUTPUT);
124 End.

[相关链接]
  1. 如果继续深入研究一下,这个东西其实是树的最小支配集。可以用贪心来解决。“随便找一个根,后续遍历树。每个点,如果它的孩子都没有真正覆盖的,同时他自己和他父亲都没有真正覆盖的,就把它的父亲覆盖,答案加一。”
    贪心算法及代码转自这里
     1 #include<iostream>
     2 #include<vector>
     3 using namespace std;
     4 int n, ans;
     5 vector<int> v[10001];
     6 bool vst[10001], have[10001];
     7 void jeo(int id, int from)
     8 {
     9     vst[id] = 1bool flag = false;
    10     for(int i = 0; i < v[id].size(); i++)
    11         if (!vst[v[id][i]])
    12             jeo(v[id][i], id), flag = flag || have[v[id][i]];
    13     if (from == -1) ans += !(have[id] || flag);
    14     else if (!have[from] && !have[id] && !flag)
    15         have[from] = 1, ans++;
    16 }
    17 int main()
    18 {
    19     scanf("%d", &n);
    20     for(int i = 1; i < n; i++)
    21     {
    22         int a, b;scanf("%d%d", &a, &b);
    23         v[a].push_back(b);
    24         v[b].push_back(a);
    25     }
    26     memset(vst, 0sizeof(bool) * (n + 1));
    27     memset(have, 0sizeof(bool) * (n + 1));
    28     ans = 0; jeo(1, -1);
    29     printf("%d\n", ans);
    30     return 0;
    31 }

[启发总结] 
  1.  动态规划的本质还是枚举,当然是很聪明的枚举。   
  2. 动态规划的子问题性可以看成是一个个单独的程序,对于当前的这个状态,我们要设计出一个合法的通道来与这些程序进队链接。就像本题,通道就是状态之间的转移,我们只关心当前的问题怎么解决,只与子问题怎么解决,那是子问题与边界的事,当前状态不关心。

  3.  状态的设计只要能保证合法性、信息的完整性、信息的可还原性即可。如我用F[I,J]中的I来定位,J作为状态编号,用这两个数字就可以确定出现在I结点出的状态。

posted @ 2012-04-16 23:09  PerSeAwe  阅读(392)  评论(0编辑  收藏  举报