概述
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 %dn",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("%dn",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("%dn",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("%dn",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("%dn",dp[0][m]);
}
return 0;
}
最后
以上就是动人月饼为你收集整理的树形DP学习小记Part1 选课 Hdu 1561 The more, The Better c++ 不撞南墙不回头——树形动态规划(树规)的全部内容,希望文章能够帮你解决树形DP学习小记Part1 选课 Hdu 1561 The more, The Better c++ 不撞南墙不回头——树形动态规划(树规)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复