关于树形DP,我这今天早上才弄懂了一道题。之前一直觉得这是什么特别高端的东西,但是就这道题而言,无非就是一个数塔的操作放在树上了。
题目大意:学校要开一个聚会。学校的教职工之间有上下级关系,为了让所有人开心,宴会组织者决定不会同时邀请一个人和他的上级(这让我想起我们昨天晚上聚餐老师不来,她怕她来了我们放不开。。。。),对于每一个人,他能给聚会带来的欢乐度有一个值,问组织者该邀请哪些人能够使宴会的欢乐度达到最大值。
首先是DP的部分(也是很无聊的一部分):每个参与者都有两种状态,一种是参加,一种是不参加。这个状态的后续影响就是如果他参加了,他的直接上司和直接下属都不能参加。我们可以用一个二维二态的数组来描述:dp[i][1]表示第i个参与者参加了,dp[i][0]表示第i个参与者没有参加。状态转移方程就是dp[i][1]=dp[i][1]+dp[i-1][0],dp[i][0]=dp[i][0]+Max(dp[i-1][0],dp[i-1][1])。这要是放在一个线性表上,同志们肯定都直接呵呵了。可所谓的树形DP就是把这个简单的操作放在树上了。
树的部分:之前很多大牛说图论的题目,难就难在一个建图。对这道题来说,DP是入门级的,很简单。但是这个建图让我纠结了许久。。。代码中是用静态链表的操作完成了一个父节点下面所有子节点的记录,对于一个子节点的操作完成之后,通过point[now].next指向下一个子节点,一直到point[now].next等于-1的时候,表示这个父节点下面所有的子节点已经遍历完成。返回父节点,再去操作这个父节点的兄弟节点。在这一点上,就是很直白的深搜的操作了。
理解代码的时候,个人建议把图画出来,再跟踪代码的数据,逐个去记录。反正我就是这么理解的。。。。。
#include<stdio.h>
#include<string.h>
#define N 6005
struct node
{
int pa,son;
int next;
}point[N];//其实从这个结构体就可以看出很多东西,这就是一个静态链表,在计算过程中遍历一个节点的所有子节点的操作也是和链表完全相同的。不过我这都是后知后觉啊。
int dp[N][2],list[N],flag[N],value[N];
int pos;
int Max(int x,int y)
{
return x>y?x:y;
}
void add(int pa,int son)
{
point[pos].pa=pa;
point[pos].son=son;
point[pos].next=list[pa];//以静态链表的形式存储一个父节点下面所有的子节点。
list[pa]=pos++;
return ;
}
void dfs(int root)//这道题说起来是树形DP,但是最没有讲的价值的就是这个DP。就是个入门级数塔的操作放在树上了。
{
if(list[root]==-1)
{
dp[root][1]=value[root];
dp[root][0]=0;
return ;
}
int now=list[root];
dp[root][0]=0;
dp[root][1]=value[root];
while(now!=-1)
{
dfs(point[now].son);
dp[root][1]+=dp[point[now].son][0];//既然取了父节点的值,子节点的值就不能再取了。
dp[root][0]+=Max(dp[point[now].son][1],dp[point[now].son][0]);//父节点的值没有取,子节点的值分取和不取两种情况,取其中较大的那种情况。
now=point[now].next;//这个子节点计算过了,就要开始计算下一个子节点了。
}
return ;
}
int main()
{
int i,n;
while(scanf("%d",&n)!=EOF)
{
for(i=1;i<=n;i++)
scanf("%d",&value[i]);//记录每一个点的值
int a,b;
pos=0;
memset(list,-1,sizeof(list));
memset(flag,0,sizeof(flag));
while(scanf("%d%d",&a,&b),a+b)
{
add(b,a);//将边加入树中
flag[a]=1;//记录a有父节点,不可能是祖节点。
}
a=1;
while(flag[a]==1)
a++;//从a往后查,遇到的第一个flag[a]的值是-1的点,这就是大名鼎鼎的祖节点了。
dfs(a);
printf("%d\n",Max(dp[a][0],dp[a][1]));
}
return 0;
}