DP起手练习7(有用的树规简单基础)

对树规简单基本认识

所谓树规,简单来说就是在树这个结构上做普通DP.它所考虑的东西只比普通DP多两点:建图和遍历.
我个人比较喜欢用邻接表存图,然后链式前向星和邻接矩阵等凭个人爱好选择;而遍历往往只有两种:根到叶子节点和叶子到根节点,一般后者使用比较广泛,而实现用递归即可.

接下来思考这道题
(由于只是DP一个基础题,所以还是只给传送门吧):

【树规模板】没有上司的舞会

思路

这道题很容易想到每个人只有两种状态:去和不去.
所以我们用 f [ i ] [ 1 ] {f[i][1]} f[i][1] f [ i ] [ 0 ] {f[i][0]} f[i][0]分别表示第 i {i} i个人去和不去时的TA与TA的所有下属的快乐度总值,最后后答案即:
a n s = m a x ( f [ r o o t ] [ 0 ] , f [ r o o t ] [ 1 ] ) ( r o o t 为 根 节 点 ) {ans=max(f[root][0],f[root][1])(root为根节点)} ans=max(f[root][0],f[root][1])(root)
进行进一步分析,我们发现:
若节点u选择去,那么它的子节点v只能不去,然后有DP转移方程:
f [ u ] [ 0 ] = f [ u ] [ 0 ] + f [ v ] [ 0 ] {f[u][0]=f[u][0]+f[v][0]} f[u][0]=f[u][0]+f[v][0];
若节点u选择不去,那它的子节点v可去可不去,然后有DP转移方程:
f [ u ] [ 1 ] = f [ u ] [ 0 ] + m a x ( f [ v ] [ 0 ] , f [ v ] [ 1 ] ) {f[u][1]=f[u][0]+max(f[v][0],f[v][1])} f[u][1]=f[u][0]+max(f[v][0],f[v][1]);
解决完DP后我们再来解决树的问题:先用邻接表连边(由根->叶),
统计每个点的入度,再扫描一次,若扫到一个节点入度为0,那这个节点就为根节点,最后由根开始Dfs一遍更新答案即可.

代码

#include
#define N 6003
using namespace std;

int n,tot,ans,root;
int a[N],f[N][2],fi[2*N],nxt[2*N],to[2*N];
bool rd[N];

inline void lian(int u,int v)//邻接表
{
	nxt[++tot]=fi[u];
	fi[u]=tot;
	to[tot]=v;
}

inline void Dfs(int u)
{
	f[u][0]=0;
	f[u][1]=a[u];//初始化
	for(int i=fi[u];i;i=nxt[i])
	{
		int v=to[i];
		Dfs(v);//搜到叶子为止
		f[u][0]+=max(f[v][0],f[v][1]);
		f[u][1]+=f[v][0];//DP核心方程
	}
	return;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&y,&x);//注意这里是反着读入!
		lian(x,y);
		if(!rd[y])rd[y]=1;//标入度
	}
	for(int i=1;i<=n;i++)//寻根
	{
		if(!rd[i])
		{
			root=i;
			break;
		}
	}
	Dfs(root);//从根开始
	ans=max(f[root][0],f[root][1]);
	printf("%d\n",ans);
	return 0;
}

以上为个人见解,望本篇博客对各位有所帮助.

你可能感兴趣的:(————DP————,树形动态规划)