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