嗯奉上今天的题解~
今天我们练习了树形DP(嗯小学生的文风还是改不了~
总体来说还是不错的。。将很多以前学过但是没写过的东西付诸于键盘上
这是很有好处的~
T1
1. 加分二叉树(btree)
【题目描述】
设一个 n 个节点的二叉树tree 的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n 为节点编
号。每个节点都有一个分数(均为正整数),记第j 个节点的分数为di,tree 及它的每个子树都有一
个加分,任一棵子树subtree(也包含tree 本身)的加分计算方法如下:
subtree 的左子树的加分× subtree 的右子树的加分+subtree 的根的分数
若某个子树为主,规定其加分为 1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出;
(1)tree 的最高加分
(2)tree 的前序遍历
这个之前就做过了,严格意义上来讲不算是树形DP
设f[i,j]表示从中序遍历从I到J得二叉树的最大加分
可得 f[i,j]=max{f[i,k-1]*f[k+1,j]+v[k] (k in [i..j]);
T2
2. 选课(course)
【题目描述】
大学里实行学分。每门课程都有一定的学分,学生只要选修了这门课并考核通过就能获得相应的
学分。学生最后的学分是他选修的各门课的学分的总和。
每个学生都要选择规定数量的课程。其中有些课程可以直接选修,有些课程需要一定的基础知识,
必须在选了其它的一些课程的基础上才能选修。例如,《数据结构》必须在选修了《高级语言程序设计》
之后才能选修。
学生不可能学完大学所开设的所有课程,因此必须在入学时选定自己要学的课程。每个学生可选
课程的总数是给定的。现在请你找出一种选课方案,使得你能得到学分最多,并且必须满足先修课优
先的原则。假定课程之间不存在时间上的冲突。
这个是很典型的树形资源分配的DP
为了资源分配的好写,我们先要把多叉转二叉~
原来以为会比较难写,后来发现其实挺好写的
然后就资源分配了
f[i,j]表示以I为根节点分配j节课可以得到的最大学分
那么。。
f[left,k]+f[right,j-cost[i]-k]+v[k] (k in [0..j-cost[i]); (自己这一脉分配资源,且将一些资源分配给其他兄弟)
f[i,j]=max{
f[right,j] (自己这一脉不要,全部资源给其他兄弟)
这样就可以AC辣~
T3
3. 警卫安排(guard)
【题目描述】
一个重要的基地被分为 n 个连通的区域。出于某种神秘的原因,这些区域以一个区域为核心,呈
一颗树形分布。
在每个区域安排警卫所需要的费用是不同的,而每个区域的警卫都可以望见其相邻的区域,只要
一个区域被一个警卫望见或者是安排有警卫,这个区域就是安全的。你的任务是:在确保所有区域都
是安全的情况下,找到安排警卫的最小费用。
这题是原题~
在建兰培训的时候有讲过~
是一种就是取还是不取,分情况什么的DP
和红书上的“没有上司的舞会”是同一类;
设 f(i,0)表示i结点被父亲看到;
f(i,1)表示i被它的儿子看到;
f(i,2)表示在i安排警卫;
则状态转移方程为:
f[i,0]=sigma(min(f[son,1],f[son,2]))
f[i,1]=sigma(min(f[son,1],f[son,2]))+f[其中一个son(这个son不在sigma里),2];
f[i,2]=sigma(min(f[son,1],f[son,2],f[son,3]));
然后就可以AC辣~
Code:
T4
和T2很像,资源分配型
没什么好说的,,
Code:
总结:
其实树形DP主要分三种:
1:资源分配型 (例:NOI贪吃的九头龙,这次的2,4两题)
主要求解方法 先多叉转二叉,然后看这个资源是自己吃一点,兄弟吃一点还是自己不吃全给兄弟
2:分情况讨论型 (例:没有上司的舞会 警卫安排)
主要求解方法 把f[i,j,0] f[i,j,1]当成状态什么的在转移好了
3:树形递推 (例:玩具
主要求解方法:按照他说的做就好了,应该算是这三种里最简单的一种了
END~
今天我们练习了树形DP(嗯小学生的文风还是改不了~
总体来说还是不错的。。将很多以前学过但是没写过的东西付诸于键盘上
这是很有好处的~
T1
1. 加分二叉树(btree)
【题目描述】
设一个 n 个节点的二叉树tree 的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n 为节点编
号。每个节点都有一个分数(均为正整数),记第j 个节点的分数为di,tree 及它的每个子树都有一
个加分,任一棵子树subtree(也包含tree 本身)的加分计算方法如下:
subtree 的左子树的加分× subtree 的右子树的加分+subtree 的根的分数
若某个子树为主,规定其加分为 1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出;
(1)tree 的最高加分
(2)tree 的前序遍历
这个之前就做过了,严格意义上来讲不算是树形DP
设f[i,j]表示从中序遍历从I到J得二叉树的最大加分
可得 f[i,j]=max{f[i,k-1]*f[k+1,j]+v[k] (k in [i..j]);
然后就可以AC辣~
Code:
const shuru='btree.in';
shuchu='btree.out';
var a:array[1..100] of int64;
f:array[0..100,0..100] of int64;
i,j,k,n:longint;
function max(a,b:int64):int64;
begin
if a>b then exit(a);
exit(b);
end;
procedure init;
begin
assign(input,shuru);
assign(output,shuchu);
reset(input);
rewrite(output);
readln(n);
for i:=1 to n do
begin
read(a[i]);
f[i,i]:=a[i];
f[i,i-1]:=1;
f[i,i+1]:=1;
end;
close(input);
end;
procedure search(x,y:longint);
var i:longint;
begin
if x>y then exit;
if x=y then begin write(x,' '); exit; end;
for i:=x to y do
if f[x,y]=f[x,i-1]*f[i+1,y]+a[i] then begin
write(i,' ');
search(x,i-1);
search(i+1,y);
exit;
end;
end;
procedure main;
begin
init;
for i:=n downto 1 do
for j:=i+1 to n do
for k:=i to j do
f[i,j]:=max(f[i,j],f[i,k-1]*f[k+1,j]+a[k]);
writeln(f[1,n]);
search(1,n);
close(output);
end;
begin
main;
end.
T2
2. 选课(course)
【题目描述】
大学里实行学分。每门课程都有一定的学分,学生只要选修了这门课并考核通过就能获得相应的
学分。学生最后的学分是他选修的各门课的学分的总和。
每个学生都要选择规定数量的课程。其中有些课程可以直接选修,有些课程需要一定的基础知识,
必须在选了其它的一些课程的基础上才能选修。例如,《数据结构》必须在选修了《高级语言程序设计》
之后才能选修。
学生不可能学完大学所开设的所有课程,因此必须在入学时选定自己要学的课程。每个学生可选
课程的总数是给定的。现在请你找出一种选课方案,使得你能得到学分最多,并且必须满足先修课优
先的原则。假定课程之间不存在时间上的冲突。
这个是很典型的树形资源分配的DP
为了资源分配的好写,我们先要把多叉转二叉~
原来以为会比较难写,后来发现其实挺好写的
然后就资源分配了
f[i,j]表示以I为根节点分配j节课可以得到的最大学分
那么。。
f[left,k]+f[right,j-cost[i]-k]+v[k] (k in [0..j-cost[i]); (自己这一脉分配资源,且将一些资源分配给其他兄弟)
f[i,j]=max{
f[right,j] (自己这一脉不要,全部资源给其他兄弟)
这样就可以AC辣~
Code:
const shuru='course.in';
shuchu='course.out';
var a:array[0..1000,0..1000] of longint;
ans:array[0..1000,0..1] of longint;
v:array[0..1000] of longint;
b:array[0..1000] of boolean;
cost:array[0..1000] of longint;
f:array[0..1000,0..1000] of longint;
tot,m,x,i,j,k,n:longint;
procedure init;
begin
assign(input,shuru);
assign(output,shuchu);
reset(input);
rewrite(output);
readln(n,m);
fillchar(b,sizeof(b),false);
for i:=1 to n do
begin
read(x,v[i]);
if x<>0 then begin
inc(a[x,0]);
a[x,a[x,0]]:=i;
end
else b[i]:=true;
end;
close(input);
end;
function max(a,b:longint):longint;
begin
if a>b then exit(a);
exit(b);
end;
procedure zhuan(p:longint);
var i,k:longint;
begin
if a[p,0]=0 then exit;
ans[p,0]:=a[p,1];
zhuan(a[p,1]);
k:=ans[p,0];
for i:=2 to a[p,0] do
begin
zhuan(a[p,i]);
ans[k,1]:=a[p,i];
k:=a[p,i];
end;
end;
procedure search(k,p:longint);
var i:longint;
begin
if f[k,p]<>0 then exit;
if p=0 then exit;
if (ans[k,1]=0) and (ans[k,0]=0) then begin
if p<>0 then f[k,p]:=v[k];
exit;
end;
if k<>0 then
begin
if (ans[k,0]=0) and (ans[k,1]<>0) then begin
search(ans[k,1],p);
search(ans[k,1],p-1);
f[k,p]:=max(f[ans[k,1],p],f[ans[k,1],p-1]+v[k]);
exit;
end;
if (ans[k,0]<>0) and (ans[k,1]=0) then begin
search(ans[k,0],p-1);
f[k,p]:=f[ans[k,0],p-1]+v[k];
exit;
end;
search(ans[k,1],p);
f[k,p]:=f[ans[k,1],p];
for i:=0 to p-1 do
begin
search(ans[k,0],i);
search(ans[k,1],p-i-1);
f[k,p]:=max(f[k,p],f[ans[k,0],i]+f[ans[k,1],p-i-1]+v[k]);
end;
exit;
end;
search(ans[0,0],p);
f[0,p]:=f[ans[0,0],p];
end;
procedure find(k,p:longint);
var i:longint;
begin
if p=0 then exit;
if k<>0 then
begin
if (ans[k,0]=0) and (ans[k,1]<>0) then begin
if f[k,p]=f[ans[k,1],p-1]+v[k] then begin writeln(k); inc(tot,v[k]); find(ans[k,1],p-1); exit; end;
if f[k,p]=f[ans[k,1],p] then find(ans[k,1],p);
exit;
end;
if (ans[k,0]<>0) and (ans[k,1]=0) then begin
writeln(k);
find(ans[k,0],p-1);
inc(tot,v[k]);
exit;
end;
if f[k,p]=f[ans[k,1],p] then begin find(ans[k,1],p); exit; end;
for i:=0 to p-1 do
begin
if f[k,p]=f[ans[k,0],i]+f[ans[k,1],p-i-1]+v[k] then begin
writeln(k);
inc(tot,v[k]);
find(ans[k,0],i);
find(ans[k,1],p-i-1);
exit;
end;
end;
end;
find(ans[0,0],p);
end;
procedure main;
begin
init;
for i:=1 to n do
if b[i] then begin
inc(a[0,0]);
a[0,a[0,0]]:=i;
end;
zhuan(0);
for i:=1 to n do
cost[i]:=1;
cost[0]:=0;
search(0,m);
writeln(f[0,m]);
find(0,m);
close(output);
end;
begin
main;
end.
T3
3. 警卫安排(guard)
【题目描述】
一个重要的基地被分为 n 个连通的区域。出于某种神秘的原因,这些区域以一个区域为核心,呈
一颗树形分布。
在每个区域安排警卫所需要的费用是不同的,而每个区域的警卫都可以望见其相邻的区域,只要
一个区域被一个警卫望见或者是安排有警卫,这个区域就是安全的。你的任务是:在确保所有区域都
是安全的情况下,找到安排警卫的最小费用。
这题是原题~
在建兰培训的时候有讲过~
是一种就是取还是不取,分情况什么的DP
和红书上的“没有上司的舞会”是同一类;
设 f(i,0)表示i结点被父亲看到;
f(i,1)表示i被它的儿子看到;
f(i,2)表示在i安排警卫;
则状态转移方程为:
f[i,0]=sigma(min(f[son,1],f[son,2]))
f[i,1]=sigma(min(f[son,1],f[son,2]))+f[其中一个son(这个son不在sigma里),2];
f[i,2]=sigma(min(f[son,1],f[son,2],f[son,3]));
然后就可以AC辣~
Code:
const shuru='guard.in';
shuchu='guard.out';
var cost:array[0..1000] of longint;
son:array[0..1000,0..1000] of longint;
f:array[0..1000,0..2] of longint;
father:array[0..1000] of longint;
root,step,x,i,j,k,n:longint;
procedure init;
begin
assign(input,shuru);
assign(output,shuchu);
reset(input);
rewrite(output);
readln(n);
for i:=1 to n do
begin
read(x,cost[x],k);
son[x,0]:=k;
for j:=1 to k do
begin
read(son[x,j]);
inc(father[son[x,j]]);
end;
readln;
end;
close(input);
for i:=1 to n do
if father[i]=0 then root:=i;
end;
function min(a,b:longint):longint;
begin
if a<b then exit(a);
exit(b);
end;
function minn(a,b,c:longint):longint;
begin
if (a<=b) and (a<=c) then exit(A);
if (b<=a) and (b<=c) then exit(B);
if (c<=a) and (c<=b) then exit(C);
end;
procedure search(k,p:longint);
var i,j,step:longint;
begin
if f[k,p]<>0 then exit;
if p=0 then
for i:=1 to son[k,0] do
begin
search(son[k,i],2);
search(son[k,i],1);
f[k,p]:=f[k,p]+min(f[son[k,i],2],f[son[k,i],1]);
end;
if p=1 then begin
f[k,p]:=maxlongint;
for i:=1 to son[k,0] do
begin
search(son[k,i],2);
step:=f[son[k,i],2];
for j:=1 to son[k,0] do
if j<>i then
begin
search(son[k,j],1);
search(son[k,j],2);
step:=step+min(f[son[k,j],1],f[son[k,j],2]);
end;
f[k,p]:=min(f[k,p],step);
end;
end;
if p=2 then begin
f[k,p]:=cost[k];
for i:=1 to son[k,0] do
begin
search(son[k,i],0);
search(son[k,i],1);
search(son[k,i],2);
inc(f[k,p],minn(f[son[k,i],0],f[son[k,i],1],f[son[k,i],2]));
end;
end;
end;
procedure main;
begin
init;
search(root,1);
search(root,2);
write(min(f[root,1],f[root,2]));
close(output);
end;
begin
main;
end.
T4
和T2很像,资源分配型
没什么好说的,,
Code:
const shuru='key.in';
shuchu='key.out';
var father,next,t,cost,key,headlist:array[0..200] of longint;
ans:array[0..200,0..1] of longint;
f:array[-200..200,-2000..200] of longint;
num,x,y,i,j,k,n,m:longint;
procedure init;
begin
assign(input,shuru);
assign(output,shuchu);
reset(input);
rewrite(output);
readln(n,m);
for i:=1 to n do
begin
readln(cost[i],key[i]);
headlist[i]:=-1;
father[i]:=-1;
end;
for i:=1 to n-1 do
begin
readln(x,y);
inc(num);
next[num]:=headlist[x];
headlist[x]:=num;
t[num]:=y;
inc(num);
next[num]:=headlist[y];
headlist[y]:=num;
t[num]:=x;
end;
close(input);
end;
procedure zhuan(k:longint);
var x,i,p:longint;
begin
if (t[headlist[k]]=father[k]) and (next[headlist[k]]=-1) then exit;
x:=headlist[k];
while t[x]=father[k] do
x:=next[x];
father[t[x]]:=k;
ans[k,0]:=t[x];
zhuan(ans[k,0]);
p:=ans[k,0];
x:=next[x];
while x<>-1 do
begin
if t[x]=father[k] then begin x:=next[x]; continue; end;
father[t[x]]:=k;
zhuan(t[x]);
ans[p,1]:=t[x];
p:=t[x];
x:=next[x];
end;
end;
function max(a,b:longint):longint;
begin
if a>b then exit(a);
exit(b);
end;
procedure search(k,p:longint);
var i:longint;
begin
if k<=0 then exit;
if p<0 then exit;
if f[k,p]<>0 then exit;
search(ans[k,1],p);
f[k,p]:=f[ans[k,1],p];
for i:=0 to p-cost[k] do
begin
search(ans[k,0],i);
search(ans[k,1],p-i-cost[k]);
f[k,p]:=max(f[k,p],f[ans[k,0],i]+f[ans[k,1],p-i-cost[k]]+key[k]);
end;
exit;
end;
procedure main;
begin
init;
if m<cost[1] then begin
writeln(0);
close(output);
halt;
end;
zhuan(1);
search(1,m);
writeln(f[1,m]);
end;
begin
main;
end.
总结:
其实树形DP主要分三种:
1:资源分配型 (例:NOI贪吃的九头龙,这次的2,4两题)
主要求解方法 先多叉转二叉,然后看这个资源是自己吃一点,兄弟吃一点还是自己不吃全给兄弟
2:分情况讨论型 (例:没有上司的舞会 警卫安排)
主要求解方法 把f[i,j,0] f[i,j,1]当成状态什么的在转移好了
3:树形递推 (例:玩具
主要求解方法:按照他说的做就好了,应该算是这三种里最简单的一种了
END~