树形DP学习小记Part1 选课 Hdu 1561 The more, The Better

2015-4-28更新,添加一个别人的题目总结:

【DP_树形DP专辑】【9月9最新更新】 - ZeroClock 

进入树形DP的学习,据说本题为树形DP入门题,难怪网上代码长得都差不多……


选课问题应该是最基本的模型了,找个能交的OJ真不容易……

问题 1376. -- 【基础算法】选课
http://www.cqoi.net:2012/JudgeOnline/problem.php?id=1376

Vijos里也有这题,不过现在改成需要输出方案了,现在水平还不行,过几天再做。

https://vijos.org/p/1180


现在跟着这篇博文学习:

c++ 不撞南墙不回头——树形动态规划(树规)


以下部分内容就转自这篇:


多叉树转二叉树

我们开两个一维数组,b[i](brother)&c[i](child)分别表示节点i的孩子和兄弟,以左孩子和右兄弟的二叉树的形式存储这样,根节点之和两个节点有关系了,状态转移的关系少了,代码自然也就好写了。

我们依旧f[i][j]表示以i为节点的根的选j门课的最大值,那么两种情况:1.根节点不选修则f[i][j]=f[b[i]][j];2.根节点选修f[i][j]=f[c[i]][k]+f[b[i]][j-k-1]+a[i](k表示左孩子学了k种课程);取二者的最大值即可。

关于本题的多叉树转二叉树,可以参考下图:


图1为本题样例对应的多叉树,图2为多叉树转二叉树之后的结果。

我对本题多叉树转二叉树的理解:

可以通过c[i]=a访问到i的最后一个孩子a,可以通过b[a]访问到a的兄弟,也就是i的倒数第二个孩子,以此类推。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#define max(a,b) ((a)>(b)?(a):(b))
#define max3(a,b,c) (max(a,max(b,c)))

const int N=320;
int n,m;
int c[N],b[N];  //c[]:means child ; b[]:means brother
int w[N],dp[N][N];

void Maketree ()   //多叉转二叉,根节点为n+1
{
    for (int i=1;i<=n;i++)
    {
        int ta,tb;
        scanf("%d%d",&ta,&tb);
        w[i]=tb;
        if (ta==0)
			ta=n+1;
        b[i]=c[ta];
        c[ta]=i;
    }
//	for (int j=0;j<=n+2;j++)
//		printf("%d %d %d\n",j,b[j],c[j]);
}

void DFS (int x,int y)
{
	if (dp[x][y]>=0) return;
	if (x==0 || y==0) {dp[x][y]=0;return;}
	DFS (b[x],y);
	for (int k=0;k<y;k++)
	{
		DFS(b[x],k); //不取该节点
		DFS(c[x],y-k-1); //取该节点
		dp[x][y]=max3 (dp[x][y] ,dp[b[x]][y] , dp[b[x]][k]+dp[c[x]][y-k-1]+w[x]);
	}
	return;
}

int main ()
{
	scanf("%d%d",&n,&m);
	memset(dp,-1,sizeof(dp));
	Maketree ();
	DFS(c[n+1],m);
	printf("%d\n",dp[c[n+1]][m]);
	return 0;
}

选课这类问题有一种优化,可以大幅降低时间复杂度,在这里看到的:http://blog.csdn.net/fp_hzq/article/details/8020625

下面的代码也参考自那里。

假设f[i][j]为以i为根的子树,包括i,选择j门课的最大值,那么有f[i][j]=max{ dp[i][a]+dp[k][b] }k是i的子树,a+b=j

优化原理可以参考:浅谈树形背包问题

#include <cstdio>  
#include <iostream>  
using namespace std;  

const int N=320;  
int dp[N][N],k[N],w[N];
int n,m;  

void TreeDP (int u,int c)
{
	if (c) for(int i=1,j;i<=n;i++)
		if (k[i]==u)   //若为u的子节点
		{
			for (j=0;j<c;j++)
				dp[i][j]=dp[u][j]+w[i];   //将u强制加入子树节点
			TreeDP (i,c-1);     //递归过程
			for (j=1;j<=c;j++)
				dp[u][j]=max(dp[u][j],dp[i][j-1]);   //利用子节点的值更新父节点
		}
}  

int main ()  
{
	int i;
	scanf("%d%d",&n,&m);
	for (i=1;i<=n;++i)  
		scanf("%d%d",&k[i],&w[i]);
	for (i=0;i<=m;++i)
		dp[0][i]=0;  
	TreeDP(0,m);  
	printf("%d\n",dp[0][m]);  
	return 0;
}  


Hdu 1561 这道题和选课问题完全一样,代码稍加修改就能过。

#include <cstdio>
#include <cstring>
#include <iostream>
#define max(a,b) ((a)>(b)?(a):(b))
#define max3(a,b,c) (max(a,max(b,c)))

const int N=320;
int n,m;
int c[N],b[N];
int w[N],dp[N][N];

void Maketree ()
{
    for (int i=1;i<=n;i++)
    {
        int ta,tb;
        scanf("%d%d",&ta,&tb);
        w[i]=tb;
        if (ta==0)
			ta=n+1;
        b[i]=c[ta];
        c[ta]=i;
    }
}

void DFS (int x,int y)
{
	if (dp[x][y]>=0) return;
	if (x==0 || y==0) {dp[x][y]=0;return;}
	DFS (b[x],y);
	for (int k=0;k<y;k++)
	{
		DFS(b[x],k); //不取该节点
		DFS(c[x],y-k-1); //取该节点
		dp[x][y]=max3 (dp[x][y] ,dp[b[x]][y] , dp[b[x]][k]+dp[c[x]][y-k-1]+w[x]);
	}
	return;
}

int main ()
{
	while (scanf("%d%d",&n,&m),n||m)
	{
		memset(dp,-1,sizeof(dp));
		memset(b,0,sizeof(b));
		memset(c,0,sizeof(c));
		Maketree ();
		DFS(c[n+1],m);
		printf("%d\n",dp[c[n+1]][m]);
	}
	
	return 0;
}

这个题还有另一种写法,参考了:http://www.cnblogs.com/wuyiqi/archive/2011/12/19/2293271.html

#include <cstdio>
#include <cstring>
#define max(a,b) ((a)>(b)?(a):(b))

const int N=220;
int n,m,num[N];
int map[N][N],dp[N][N];
bool visit[N];

void DFS (int p)
{
	int i,j,k;
	visit[p]=true;
	for (i=1;i<=num[p];i++)
	{
		int t=map[p][i];
		if (visit[t]==false)
			DFS(t);
		for (j=m;j>=2;j--) //选择1个的状态不用更新了,因为是强制要加进去的,即必须先选择的
			for (k=1;k<j;k++)
				if (dp[t][j-k]!=-1 && dp[p][k]!=-1)
					dp[p][j]=max(dp[p][j],dp[p][k]+dp[t][j-k]);
	}
}

int main ()
{
	while (scanf("%d%d",&n,&m),n||m)
	{
		memset(visit,false,sizeof(visit));
		memset(dp,-1,sizeof(dp));
		memset(num,0,sizeof(num));
		dp[0][1]=0;
		for (int i=1;i<=n;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			dp[i][0]=0;
			dp[i][1]=b;
			map[a][++num[a]]=i;
		}
		m++;//增加根节点
		DFS (0);
		printf("%d\n",dp[0][m]);
	}
	return 0;
}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值