其他OJ 树型DP 选课

在朱全民的PPT介绍的一个树型DP经典题,《选课》,中文题目,不结束

找了很久找到了可以提交的OJ,重庆八中 http://www.cqoi.net:2012/JudgeOnline/problem.php?id=1376

简单分析一下:

1.建树,不要用一般的孩子表示法,这里要讲森林转为二叉树处理才能强劲有力,所以用(左)孩子(右)兄弟法建树

2.建树之后就可以DP,DP的策略写在代码中了,不多说

对于树型DP的初步感觉

1.建树很重要(跟图论里面构图很重要,构图失败基本上整个算法失败了)

2.树这种结构比较特殊,要找到特殊型入手,一是从属关系,即孩子是属于某个双亲的,二是平行关系,兄弟之间是平行的。因而一个节点的信息往往可以由其孩子的信息得到。同样的,一个节点的信息也有可能传递给孩子

3.唯一性:一个结点可能有多个孩子,但是双亲一定是唯一的,很多问题的突破就在此

/*
用孩子兄弟法建树
1.对于树的一个节点,分三个域,数据域,第一个孩子域,第一个右兄弟域
2.接下来每一行读两个信息,j,s。在第i行中,j表示i的先修课程,也就是i是j的孩子
  先把j之前的第一个孩子保存下来,为k,可知i和k是兄弟关系,所以k的第一个右兄弟更新为i
  同时i更新为j的第一个孩子
  更新第一个孩子和第一个右兄弟都用数组来模拟链表实现
  建树后得到的是森林,有一些元素是森林里面的树根,还要设置一个虚拟的节点来作为这些树根的双亲,
  所以建树过程要记录哪些元素是树根,不过还好,输入中已经有了提示,不用另外记录。输入中,树根的双亲都定为0
  这刚好符合我们的要求,我们就把虚拟的节点设为0,同样用孩子兄弟法将森林合并为一棵树
*/

#include <cstdio>
#include <cstring>
#define N 310
#define M 310
#define INF 0x3f3f3f3f
#define max(a,b) a>b?a:b

int fc[N],rb[N],data[N]; //第一个孩子,第一个右兄弟,数据域
int dp[N][M]; //dp[i][j]以i为根,选j门课的最大值
int n,m;

void build()
{
    memset(fc,0,sizeof(fc));
    memset(rb,0,sizeof(rb));
    data[0]=0;
    for(int i=1; i<=n; i++)
    {
        int j,k;
        scanf("%d%d",&j,&data[i]);
        k=fc[j]; rb[i]=k; fc[j]=i;
    }
}

int dfs(int i ,int j)
{
    if(i==0 || j==0) return dp[i][j]=0;
    if(dp[i][j]!=-1) return dp[i][j];

    /*
    dp[i][j]表示以i为根的子树要选择j个课程的最大值(此时的树不是原始的森林而是孩子兄弟法表示的二叉树)
    dp策略为:对于当前节点,其左指针是它的第1个孩子,右指针是它的第一个兄弟,左边是从属关系,右边是平行关系
              如果不选当前节点,那么它整个左子树都不能选了,因为整个左子树都是它的后代,
              那么将由它的右子树分担选j个课程的责任(左边从属关系,右边平行关系,不受当前节点影响)
              如果选了当前节点,那么剩下的课程就是j-1个,这时候即可以到左子树继续选,也可以到右子树继续选
              且要满足两棵子树选课之和为j-1
    */
    dp[i][j]=0;
    int c=fc[i] , b=rb[i];
    //选当前节点,那么孩子和兄弟都要考虑
    for(int k=0; k<j; k++) //枚举左边分担的个数
    {
        dfs(c,k);
        dfs(b,j-1-k);
        dp[i][j] = max(dp[i][j] , data[i]+dp[c][k]+dp[b][j-1-k]);
    }
    //不选当前节点,那么只能从它的兄弟开始选,但是它必须得有兄弟
    dfs(b,j);
    dp[i][j] = max(dp[i][j] , dp[b][j]);

    return dp[i][j];
}

void solve()
{
    memset(dp,-1,sizeof(dp));
    dfs(fc[0],m);
    printf("%d\n",dp[fc[0]][m]);
}

int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        build(); //建树
        solve();
    }
    return 0;
}
原文地址:https://www.cnblogs.com/scau20110726/p/2979417.html