285. 没有上司的舞会
题目描述
某公司有N名职员,编号为1~N。
他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。
每个职员有一个快乐指数,用整数 Hi 给出,其中 1≤i≤N。
现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。
在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。
输入格式
第一行一个整数N。
接下来N行,第 i 行表示 i 号职员的快乐指数Hi。
接下来N-1行,每行输入一对整数L, K,表示K是L的直接上司。
输出格式
输出最大的快乐指数。
数据范围
1≤N≤6000,
−128≤Hi≤127
输入样例:
7
1
1
1
1
1
1
1
1 3 //即,3->1
2 3
6 4
7 4
4 5
3 5
输出样例:
5
分析
整个公司的职员组成一棵树,每个职员就是树上的一个结点,对于每个结点,有0 1两种状态,选就是1,不选就是0。
状态表示:
f[u][0]
:以结点u为根的子树,并且不选u这个点的符合要求的各种方案
f[u][1]
:以结点u为根的子树,并且选择u这个点的符合要求的各种方案
属性: Max,f[u][0]
或者f[u][1]
的值就是各种符合要求的方案的最大值
求从u结点开始的子树满足要求的方案最大值,最终结果肯定在f[u][0]
和f[u][1]
中选择,选择其最大值。
- 考虑是否选择结点u
- 递归地考虑父结点u各个子结点si,递归结束的条件就是到达叶子结点。
以父结点u为根节点考虑 和 以子节点s为根节点考虑,这二者的方法是相同的,且相互独立的。
且父结点的值需要用到子节点的值。
假设以结点u为根节点的子树有多个子节点si,考虑它的每个子节点si,可以再看成以子节点si为根的子子树。
- 如果选择结点u的话,子结点si一定不能选。
f [ u ] [ 1 ] = ∑ ( f [ s i , 0 ] ) f[u][1]=∑(f[si,0]) f[u][1]=∑(f[si,0]) - 如果不选择结点u的话,子结点可选可不选,选择其中的最大值即可。
f [ u ] [ 0 ] = ∑ m a x ( f [ s i , 0 ] , f [ s i , 1 ] ) f[u][0]=∑max(f[si,0],f[si,1]) f[u][0]=∑max(f[si,0],f[si,1])
注意:
由数据的输入可知,我们并不知道树的根节点是谁,因此还需要自己处理得到。
得到根节点root后,看成以根节点的子树,所以最后只用考虑root根节点是否选择,输出f[root][0]
和f[root][1]
的较大者即可。
代码实现
#include <iostream>
#include <cstring>
#define read(x) scanf("%d",&x)
using namespace std;
const int N=6010;
int hpy[N];
int h[N],e[N],ne[N],idx;//树,N个顶点,N-1条边
//next数组记录后继结点,通过idx记录
int pre[N];//pre数组记录前驱结点,直接通过结点编号记录
int dp[N][2];
void add(int a,int b)//a->b的边
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
pre[b]=a;
}
void dfs(int u) //判断以该点为根的子树的最大happy值
{
"初始化,是否选该结点:dp[u][1]和dp[u][0]"
"选中该结点,加上该happ值,不选中时,dp[u][0]=0"
dp[u][1]=hpy[u];
"递归结束的条件,遇到叶子结点,它没有子节点了。只进行初始化,然后就往上返回"
for (int i=h[u];~i;i=ne[i]) {
int j=e[i]; //找它的子节点
dfs(j); //求子节点的最大happy值
//考虑以u为根的子树,是否选子节点?
dp[u][0]+=max(dp[j][0],dp[j][1]); //不选u时,子节点可选可不选,取最大值。
dp[u][1]+=dp[j][0]; //选u时,子节点必不能选。
}
}
int main()
{
memset(h,-1,sizeof h);
int n;
read(n);
for (int i=1;i<=n;i++) read(hpy[i]);
//存树
int l,k; //k是l的直接上司,即k->l的边
for (int i=1;i < n;i++) { //n个顶点,所以共n-1条边,不能取等号
read(l),read(k);//先输入的是子节点
add(k,l); //存数k->l的边
}
//找树的根节点,记录结点编号,只有根节点没有父结点,pre[rt]==0
int rt=1;
while (pre[rt]) rt++;
dfs(rt);
int res=max(dp[rt][0],dp[rt][1]);
printf("%d",res);
return 0;
}
一定要记得main循环第一行中的初始化,常犯错误。
dfs递归中,递归结束的条件就是碰见叶子结点,由于没有子节点了,所以他也就不会进入for循环,进行初始化之后就往上返回了。
树形结构,不存在一个点有多个入度,只有一个入度和最多一个出度,保证了每个点只会被初始化一次,只会经历一次递归。