其他OJ 树型DP “访问”艺术馆
提交地址:http://www.cqoi.net:2012/JudgeOnline/problem.php?id=1286
这题是OI的经典题,不难,注意一点,原题是用文件输入输出的,但是这里的提交直接标准输入输出即可
这题的题意很清晰,明说了是二叉树(而且只能在两个孩子的节点和叶子节点)。
注意输入给出的信息,对于一对数据,a,b,a指通过走廊的时间,那是不是树中边的信息呢?不是的,应该是点的信息。树中每一个点都应该包含两个信息,就是时间花费和它有多少张画,对于非叶子节点而言,它的画数都是0,而时间是有的,对于叶子节点,除了有画数外,它也是有时间花费的
另外,本题读题要仔细,它是说在警察来之前就要离开,而不能在他来的时候离开,所以读入警察的到达时间后减1再开始计算
另外偷东西是要进去和出来的,所以如果经过了某个点往下走,就必定会经该店返回(树的性质可知),所以花费其实是两倍,所以我们在一开始保存点的花费的时候,就直接将时间花费保存为两倍,这样就相当于做到了返回
最后说一次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 <utility> using namespace std; #define N 550 #define T 650 #define max(a,b) ((a)>(b)?(a):(b)) int n; struct node { int lch,rch,cost,val; }t[N]; pair<int,int>a[N]; int dp[N][T]; void build(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; build(++m); t[rt].rch=m+1; build(++m); } int dfs(int rt ,int time) { if(dp[rt][time]!=-1) return dp[rt][time]; if(time==0) return dp[rt][time]=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; scanf("%d",&time); time--; n=0; while(scanf("%d%d",&a[n].first,&a[n].second)!=EOF) n++; int m=0; build(m); memset(dp,-1,sizeof(dp)); dfs(0,time); printf("%d\n",dp[0][time]); return 0; }