题目描述:
解题思路:
根据题意可得这道题就是在一棵树上选择若干个点,使得点权最大,其中选点满足两个条件:
- 选了父节点就不能选子节点
- 选了子节点就不能选父节点
显然,题目的当务之急是如何在程序中存储一棵树,或者说存储一张图,这时候通俗的邻接矩阵就在空间上出现了缺陷,因此我们要用到链式前向星。
链式前向星:
链式前向星是一种灵活的存储结构,我们需要用到结构体在程序中实现。
有一张图点与点之间边的关系为:
1 3
2 3
6 4
7 4
4 5
3 5
则实际上图为:
设结构体数组
e
e
e 表示图中点与点之间边的关系,其中包含
h
e
a
d
,
x
,
y
,
n
e
x
t
head,x,y,next
head,x,y,next,则
e
.
x
e.x
e.x 表示起始点,
e
.
y
e.y
e.y 表示目标点,
e
.
n
e
x
t
e.next
e.next 表示与
e
.
x
e.x
e.x 同起始点的下一条边的序号,
e
.
h
e
a
d
e.head
e.head 表示
e
.
x
e.x
e.x 的第一条边的序号。
如图,即为链式前向星存储一张图时的状态。
我们找到编号为4的起始点,根据表头
e
4
.
h
e
a
d
=
4
e_4.head=4
e4.head=4可知,以
4
4
4 点作为起始点的第一条边是编号为
4
4
4 的边,这条边的目标点为
e
4
.
y
=
7
e_4.y=7
e4.y=7,即编号为
7
7
7 的点。
然后观察以
4
4
4 作为起始点的下一条边,那么边的编号即为
e
4
.
n
e
x
t
=
3
e_4.next=3
e4.next=3 ,可知编号为
3
3
3 的边是
4
4
4 的第二条边,目标点为
e
3
.
y
=
6
e_3.y=6
e3.y=6。
若再看向下一条边,我们发现
e
3
.
n
e
x
t
=
0
e_3.next=0
e3.next=0,也就是说没有下一条边了,即结束对
4
4
4 点的遍历。
由此,通过遍历根节点的所有边,我们便可以得到子节点,以此反复,我们便可以存储并可遍历一张图。
struct note
{
int x,y,next;
} e[6010];
int head[6010]={0},tot=0; //tot表示边的编号
void add(int x,int y) //将X,Y之间建立一条有向边
{
tot++;
e[tot].x=x;
e[tot].y=y;
e[tot].next=head[x];
head[x]=tot;
}
树状DP:
解决的了存图的问题后,我们就要考虑在这颗树上进行DP了。
设
f
i
,
1
/
0
f_{i,1/0}
fi,1/0 表示当考虑到第
i
i
i 个点时选
/
/
/ 不选时,以
i
i
i 为根的子树的最大点权和。
当选父节点时,它的子节点不能选;当不选父节点时,它的子节点亦可选亦可不选。
状态转移方程不难给出:
f
i
=
{
∑
j
f
j
,
0
选
第
i
个
点
,
j
为
i
的
儿
子
∑
j
m
a
x
(
f
j
,
0
,
f
j
,
1
)
不
选
第
i
个
点
,
j
为
i
的
儿
子
f_i=\begin{cases} \sum_{j}f_{j,0}&选第i个点, j为i的儿子\\ \sum_{j}max(f_{j,0},f_{j,1})&不选第i个点,j为i的儿子 \end{cases}
fi={∑jfj,0∑jmax(fj,0,fj,1)选第i个点,j为i的儿子不选第i个点,j为i的儿子
由于树本身具有递归的性质,因此我们找到一个根节点,从根节点开始通过链式前向星遍历它的子树,递归进行DP。
CODE:
#include <iostream>
#include <cstring>
using namespace std;
struct note
{
int x,y,next;
} e[6010];
int head[6010]={0},tot=0;
void add(int x,int y) //链式前向星
{
tot++;
e[tot].x=x;
e[tot].y=y;
e[tot].next=head[x];
head[x]=tot;
}
int n,ri[6010]={0},f[6010][2]={0};
bool vis[6010]={false};
void dp(int s) //s为当前树的根节点
{
f[s][1]=ri[s];
int i=head[s]; //i为s的第一条边编号
while(i)
{
dp(e[i].y); //处理这条边的目标点,将子树做一遍递归
f[s][1]=f[e[i].y][0]+f[s][1];
f[s][0]=max(f[e[i].y][1],f[e[i].y][0])+f[s][0];
i=e[i].next; //下一条边
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>ri[i];
int a,b;
for(int i=1;i<n;i++)
{
cin>>a>>b;
add(b,a);
vis[a]=true;
}
for(int i=1;i<=n;i++)
{
if(vis[i]) continue; //寻找根节点,即入度为0的点
dp(i);
cout<<max(f[i][1],f[i][0]);
break;
}
return 0;
}