【前方高能】!后效性!

今天校内测

由于忽视了后效性的问题,很happy地爆炸了

然后就华华丽丽地炸掉了树形dp。。AK→220

然后悲剧的想到因为没有消掉后效性而炸掉的dp题好像不是第一个了QAQ

所以这波就讲关于dp中消除后效性的问题

dp中有一个很重要的问题就要保证无后效性,在状态转移的过程中非常关键

简单说,后效性就是之后要求的决策不会对当前要求决策产生干扰

所以如果没有处理好后效性的问题,dp很有可能就是白写了

比较常见的就是迭代之类的问题

,,今天就是在迭代的时候崩掉的

就以这题为例↓

3.Nearby Cows
问题描述:
给出一棵 N 个点的树,每个点上都有牛,问每个点 K 步范围内有多少牛。牛的数量包括这个点本身。
输入格式:
第一行为 N K
以下 2..N 行,每行两个整数,表示两个点相连。点的范围在 1..N
以下 N 行,每行一个整数, 表示每个点上牛的数量。按点的编号的顺序给出。数量范围在 0..1000
输出格式:
按点的编号从小到大输出牛的数量。
输入样例:
6 2
5 1
3 6
2 4
2 1
3 2
1
2
3
4
5
6
输出样例:
15
21
16
10
8
11
数据范围:
1 <= N <= 100,000
1 <= K <= 20

其实是道很水的树形dp

状态转移方程很好推

把树给定下来之后,先从叶子节点往根节点上推一波,f[i][j]表示以i为根节点的子树内从i开始恰好走j步的节点的权重,假定从i到i要走1步

f[i][j]=∑f[i的儿子节点][j-1]

刷完之后

迭代一遍把f[i][j]的含义变成以i为根节点,j步范围内的权重和,就是前缀和的原理

for (int i=1;i<=n;i++)
 for (int j=2;j<=k;j++)
  f[i][j]+=f[i][j-1];

然后从根节点往叶子节点推一发,把除了子树内的其他点也迭代上

所以f[i][j]=f[i的父亲节点][j-1]-f[i][j-2]后面减去的东西是因为i的子树内的东西也被i的父节点算过一次,重复了

代码在下面↓

 var link,fa,que,a:array[0..100005]of longint;
     son,next:array[0..200005]of longint;
     vis:array[0..100005]of boolean;
     f:array[0..100005,0..25]of longint;
     n,k,tot,head,tail:longint;
 procedure add(x,y:longint);
  begin
   inc(tot);son[tot]:=y;next[tot]:=link[x];link[x]:=tot;
  end;
 procedure init;
  var i,j,x,y:longint;
   begin
    assign(input,'nearcows.in');reset(input);
    assign(output,'nearcows.out');rewrite(output);
    fillchar(link,sizeof(link),0);
    readln(n,k);tot:=0;inc(k);
    for i:=1 to n-1 do
     begin
      readln(x,y);
      add(x,y);add(y,x);
     end;
    for i:=1 to n do readln(a[i]);
   end;
 procedure main;
  var i,j,x,t:longint;
   begin
    fillchar(vis,sizeof(vis),0);
    fillchar(que,sizeof(que),0);
    head:=0;tail:=1;que[1]:=1;vis[1]:=true;
    fillchar(fa,sizeof(fa),0);
    while head<>tail do
     begin
      inc(head);
      x:=que[head];
      j:=link[x];
      while j<>0 do
       begin
        if not vis[son[j]] then begin
                                 inc(tail);
                                 que[tail]:=son[j];
                                 vis[son[j]]:=true;
                                 fa[son[j]]:=x;
                                end;
        j:=next[j];
       end;
     end;
   fillchar(f,sizeof(f),0);
   for i:=n downto 1 do
    begin
     x:=que[i];
     f[x,1]:=a[x];
     j:=link[x];
     while j<>0 do
      begin
       if fa[x]<>son[j] then
        begin
         for t:=1 to k-1 do
          f[x,t+1]:=f[x,t+1]+f[son[j],t];
        end;
       j:=next[j];
      end;
    end;
   for i:=1 to n do for j:=2 to k do f[i,j]:=f[i,j-1]+f[i,j];
   for i:=1 to n do
    begin
     x:=que[i];
     if fa[x]=0 then continue;
     for t:=1 to k-1 do
      f[x,t+1]:=f[x,t+1]-f[x,t-1]+f[fa[x],t];
    end;
   end;
 procedure print;
  var i:longint;
   begin
    for i:=1 to n do writeln(f[i,k]);
    close(input);close(output);
   end;
 begin
  init;
  main;
  print;
 end.

从头看到尾,感觉并没有什么问题==

====== ======

事实上,这个程序是WA的

问题在这里↓

 

把循环改成for t:=k-1 downto 1 do 就没问题了,原因就是后效性

因为每次求的时候要调用f[x,t-1],且因为要减去的f[x,t+1]和f[fa[x],t]的重复部分是以x为根的子树内的东西,而每次更新之后f[x,t+1]都不再只是子树内的权重和,而是所有从x为起点t+1步内的权重和,所以要保证每次掉的f[x,t-1]还没被更新,t只能反着枚举,即for t:=k-1 downto 1 do

下面的是AC代码↓

 

 var link,fa,que,a:array[0..100005]of longint;
     son,next:array[0..200005]of longint;
     vis:array[0..100005]of boolean;
     f:array[0..100005,0..25]of longint;
     n,k,tot,head,tail:longint;
 procedure add(x,y:longint);
  begin
   inc(tot);son[tot]:=y;next[tot]:=link[x];link[x]:=tot;
  end;
 procedure init;
  var i,j,x,y:longint;
   begin
    assign(input,'nearcows.in');reset(input);
    assign(output,'nearcows.out');rewrite(output);
    fillchar(link,sizeof(link),0);
    readln(n,k);tot:=0;inc(k);
    for i:=1 to n-1 do
     begin
      readln(x,y);
      add(x,y);add(y,x);
     end;
    for i:=1 to n do readln(a[i]);
   end;
 procedure main;
  var i,j,x,t:longint;
   begin
    fillchar(vis,sizeof(vis),0);
    fillchar(que,sizeof(que),0);
    head:=0;tail:=1;que[1]:=1;vis[1]:=true;
    fillchar(fa,sizeof(fa),0);
    while head<>tail do
     begin
      inc(head);
      x:=que[head];
      j:=link[x];
      while j<>0 do
       begin
        if not vis[son[j]] then begin
                                 inc(tail);
                                 que[tail]:=son[j];
                                 vis[son[j]]:=true;
                                 fa[son[j]]:=x;
                                end;
        j:=next[j];
       end;
     end;
   fillchar(f,sizeof(f),0);
   for i:=n downto 1 do
    begin
     x:=que[i];
     f[x,1]:=a[x];
     j:=link[x];
     while j<>0 do
      begin
       if fa[x]<>son[j] then
        begin
         for t:=1 to k-1 do
          f[x,t+1]:=f[x,t+1]+f[son[j],t];
        end;
       j:=next[j];
      end;
    end;
   for i:=1 to n do for j:=2 to k do f[i,j]:=f[i,j-1]+f[i,j];
   for i:=1 to n do
    begin
     x:=que[i];
     if fa[x]=0 then continue;
     for t:=k-1 downto 1 do
      f[x,t+1]:=f[x,t+1]-f[x,t-1]+f[fa[x],t];
    end;
   end;
 procedure print;
  var i:longint;
   begin
    for i:=1 to n do writeln(f[i,k]);
    close(input);close(output);
   end;
 begin
  init;
  main;
  print;
 end.

 所以在各种dp题目中,正着刷和反着刷可能会得到截然不同的结果,一定要注意

鸣谢帮忙找到原代码错误之处的晗翀学姐!!

【写的有漏洞的,欢迎路过大神吐槽】

2016-08-12 16:49:29

Ending.

posted @ 2016-08-12 16:51  白云千载空悠悠  阅读(802)  评论(6编辑  收藏  举报