其他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;

}

 

 

你可能感兴趣的:(dp)