树形DP(访问艺术馆)

来源:互联网 发布:威海淘宝培训学校 编辑:程序博客网 时间:2024/04/30 12:30

    皮尔是一个出了名的盗画者,他经过数月的精心准备,打算到艺术馆盗画。艺术馆的结构,每条走廊要么分叉为二条走廊,要么通向一个展览室。皮尔知道每个展室里藏画的数量,并且他精确地测量了通过每条走廊的时间,由于经验老道,他拿下一副画需要5秒的时间。你的任务是设计一个程序,计算在警察赶来之前,他最多能偷到多少幅画。

第1行是警察赶到得时间,以s为单位。第2行描述了艺术馆得结构,是一串非负整数,成对地出现:每一对得第一个数是走过一条走廊得时间,第2个数是它末端得藏画数量;如果第2个数是0,那么说明这条走廊分叉为两条另外得走廊。数据按照深度优先得次序给出,请看样例

输出偷到得画得数量

60

7 0 8 0 3 1 14 2 10 0 12 4 6 2

2

s<=600

走廊的数目<=100


这题是OI的经典题,不难,注意一点,原题是用文件输入输出的,但是这里的提交直接标准输入输出即可

这题的题意很清晰,明说了是二叉树(而且只能在两个孩子的节点和叶子节点)。

注意输入给出的信息,对于一对数据,a,b,a指通过走廊的时间,那是不是树中边的信息呢?不是的,应该是点的信息。树中每一个点都应该包含两个信息,就是时间花费和它有多少张画,对于非叶子节点而言,它的画数都是0,而时间是有的,对于叶子节点,除了有画数外,它也是有时间花费的

另外偷东西是要进去和出来的,所以如果经过了某个点往下走,就必定会经该店返回(树的性质可知),所以花费其实是两倍,所以我们在一开始保存点的花费的时候,就直接将时间花费保存为两倍,这样就相当于做到了返回

 

最后说一次DP思想

dp[rt][time]表示在rt这个点,剩下time时间能偷到最多的画

1.虽然时间有time,但要先花费掉通过该点的时间,tt=time-t[rt].cost

2.然后在tt时间内dp,dp[rt][time]= max{ dp[lch][i] + dp[rch][tt-i] }

这个方程很容易理解,一半时间去左边偷一半时间去右边偷,两者相加,再取最大值

3.而对于叶子节点,同样有时间花费的,所以同样先计算出tt,但是计算出tt后不用再递归了,而是计算在tt时间内最多可以拿多少画,并且这里肯定是尽量拿,只要时间还够的

但是注意一点,不能tt/5,因为时间够,但是可能画不够。。。我就是这样纠结了一下,才想起这个问题

至于怎么建树,输入已经是按照前序遍历序列给出的,那么就顺着输入,来个递归建树即可


#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std ;
#define N 6500
#define T 650
int n ;
 struct node 
{
int lch,rch,val,cost; 
}t[N];
struct aa
{
int first,second;
}a[N]; 
int dp[N][T];


void buile(int &m)     //建树 
{
int rt = m ;
t[rt].cost = 2*a[rt].first ; t[rt].val = a[rt].second ;     //把经过走廊的来回时间花费算出来。 
if(a[m].second)       //如果是叶子 
{
t[rt].lch=t[rt].rch=-1;    //没有左右孩子 
return ;
}
t[rt].lch=m+1;         //左孩子是下一个节点 
buile(++m);          //以左孩子为根节点继续建树 
t[rt].rch=m+1;       //右孩子是下一个节点
buile(++m);          //以右孩子为根节点继续建树
}


int dfs(int rt, int time)      // dp[rt][time]表示,在rt这个节点(岔口),剩余time时能偷到的最大副画 
{
if(dp[rt][time]!=-1) return dp[rt][time];    //如果有答案,就结束返回 
if(time==0)       return dp[rt][time]=0;      //时间还剩0,则一副也偷不到 
if(t[rt].lch==-1)          //是叶子 
{
int c;
if( t[rt].val*5 <= time-t[rt].cost )   c =t[rt].val ;   //如果见过走廊后的剩余时间可以全部偷完画,就赋值 
else  c = (time-t[rt].cost)/5 ;          //否则偷不完 
return    dp[rt][time]=c ;      //返回结果 
}  
dp[rt][time]=0;       //初始化 
int tt = time - t[rt].cost ;    //经过走廊后的剩余时间, 
for(int i = 0; i <= tt ; i++)    //枚举一部分往左孩子去偷,剩余部分时间往右孩子那边去偷 
{
 int s1=dfs(t[rt].lch,i);       //往做孩子方向偷的最多的画 
 int s2=dfs(t[rt].rch,tt-i);     //往右孩子那边偷的最多的画 
dp[rt][time]=max( dp[rt][time] , s1+s2 );   //比较。。 
}     
    return dp[rt][time] ;          
}


int main()
{
   int time , n = 0;
   scanf("%d",&time); 
   while(scanf("%d%d",&a[n].first,&a[n].second)!=EOF) n++ ;
    int m=0;
    buile(m);     //以0为根节点建树 
  memset(dp,-1,sizeof(dp));
  dfs(0,time);
  printf("%d",dp[0][time]);  //0节点,剩余time时间能偷到的最大画就是结果 
  return 0;
}