P1352 SSL 1607 没有上司的晚会(链式前向星)

题目描述:

题目传送门


解题思路:

根据题意可得这道题就是在一棵树上选择若干个点,使得点权最大,其中选点满足两个条件:

  1. 选了父节点就不能选子节点
  2. 选了子节点就不能选父节点

显然,题目的当务之急是如何在程序中存储一棵树,或者说存储一张图,这时候通俗的邻接矩阵就在空间上出现了缺陷,因此我们要用到链式前向星。

链式前向星:

链式前向星是一种灵活的存储结构,我们需要用到结构体在程序中实现。

有一张图点与点之间边的关系为:

1 3
2 3
6 4
7 4
4 5
3 5

则实际上图为:

P1352 SSL 1607 没有上司的晚会(链式前向星)

设结构体数组 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 的第一条边的序号。
P1352 SSL 1607 没有上司的晚会(链式前向星)
如图,即为链式前向星存储一张图时的状态。
我们找到编号为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​={∑j​fj,0​∑j​max(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;
}
上一篇:MySQL从库报Error_code: 2005


下一篇:SSL连接是什么意思