• 【前方高能】!后效性!


    今天校内测

    由于忽视了后效性的问题,很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.

  • 相关阅读:
    CodeForces 55D Beautiful numbers(数位dp+数学)
    hdu 2089 不要62(数位dp入门)
    Git版本控制
    Git初始化-添加提交以及查看状态
    linux-高并发与负载均衡-lvs-3种模型推导
    Scrapy中选择器的用法
    Scrapy命令行详解
    Scrapy框架基本用法讲解
    MaxCompute教程
    Scrapy安装报错
  • 原文地址:https://www.cnblogs.com/wry0112/p/5765609.html
Copyright © 2020-2023  润新知