树形动态规划练习《蓝桥杯 结点选择》

问题描述

有一棵 n 个节点的树,树上每个节点都有一个正整数权值。如果一个点被选择了,那么在树上和它相邻的点都不能被选择。求选出的点的权值和最大是多少?

输入格式

第一行包含一个整数 n 。

接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值。

接下来一共 n-1 行,每行描述树上的一条边。

输出格式

输出一个整数,代表选出的点的权值和的最大值。

样例输入

5
1 2 3 4 5
1 2
1 3
2 4
2 5

样例输出

12

样例说明

  选择3、4、5号点,权值和为 3+4+5 = 12 。

  数据规模与约定

  对于20%的数据, n <= 20。

  对于50%的数据, n <= 1000。

  对于100%的数据, n <= 100000。

  权值均为不超过1000的正整数。

解题过程

  刚学习完树形动态规划的原理,所以乍一看就知道此题应该用树形动态规划解决。分两步:1、建树。2、动态规划。

  刚开始选择的存储结构是二维数组,既每一行表示树的一层,每一列表示该层(行)的所有节点;记录下树的最大层数,从最后一层开始改变每个节点的状态,最后从根节点中获取最优解。

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#define M 100010 //数组最大长度

int fu[M],hz[M][M],shu[M][M],pow[M],f[M][2];
//父节点数组; 孩子数组hz[i][0]第i个节点的孩子数,hz[i][j](j>0)表示i节点的第j个孩子
//树二维数组,shu[i][0]表示第i层节点数,shu[i][j](j>0)表示第i层的第j个节点;
//pow[]权值数组,p[i]表示第i个节点的权值
//f[i][1]保留节点i时最大权值,f[i][0]不保留节点i时的最大权值

int main()

{

        int n,i,j,u,v;

        memset(fu,0,sizeof(fu));

        memset(hz,0,sizeof(hz));

        memset(shu,0,sizeof(shu));

        memset(f,0,sizeof(f));

        scanf("%d",&n);

        for(i=1;i<=n;i++)scanf("%d",&pow[i]);

        for(i=1;i<n;i++)

        {

                scanf("%d%d",&u,&v);

                fu[v]=u;

                hz[u][0]++;

                hz[u][hz[u][0]]=v;

        }

        //建树

        int x,maxlev=-1,s;

        for(i=1;i<=n;i++)

        {

                x=fu[i];

                s=1;

                while(x!=0){s++;x=fu[x];}

                shu[s][0]++;

                shu[s][shu[s][0]]=i;

                if(s>maxlev)maxlev=s;

        }

        //动态规划

        int now,k,a,b;  

        for(i=maxlev;i>0;i--)

        {

                 for(j=1;j<=shu[i][0];j++)

                 {

                          now=shu[i][j];

                          if(hz[now][0]==0)

                          {

                                  f[now][0]=0;

                                  f[now][1]=pow[now];

                          }

                          else

                          {

                                  for(k=1;k<=hz[now][0];k++)

                                  {

                                           a=f[hz[now][k]][0];

                                           b=f[hz[now][k]][1];

                                           f[now][1]+=a;

                                           if(b>a)a=b;

                                           f[now][0]+=a;

                                  }

                          }

                 }

        }

        int sum=0;

        for(i=1;i<=shu[1][0];i++)

        {

                now=shu[1][i];

                a=f[now][0];b=f[now][1];

                if(b>a)a=b;

                sum+=a;

        }

        printf("%d\n",sum);

        return 0;

}

  

按理说这个算法是可行的,但是再提交答案时,居然发生运行错误,我看了看内存使用率非常大,返回题目看了数据规模,节点数n<=100000,也就意味着要用二维数组存储树的话,二维数组至少定义为shu[100000][100000],占用了非常大的控件资源。再者,题目给n个顶点,n-1条边,也就意味着树没有孤立点,并且有且仅有一个根节点,可见每一层的节点很多时候是远少于100000的,所以应该改用动态存储结构。

树的存储结构

  《1》、双亲表示法

      假设以一组连续空间存储树的节点,同时在每个节点中附设一个指示器指示其双亲节点在链表中的位置,其形式说明如下:

#define MAX_TREE_SIZE 100

typedef struct PTNode{//节点结构

    TElemType data;

    int parent;//双亲位置

}PTNode;

typedef struct{    //树结构

    PTNode nodes[MAX_TREE_SIZE];

    int r,n; //根节点位置和节点数

}PTree;

  

  这种存储结构利用了每个节点(除根节点以外)只有唯一双亲的性质。PARENT(T,x)操作可以在常数时间内实现。反复调用PARENT操作,直到遇见无双亲的节点时,便找到了树的根,这个就是ROOT(x)的过程。但是,在这种表示法中,求节点的孩子时需要遍历整个结构。

  《2》、孩子表示法

    这里主要给出一种类似于邻接表的表示法。把每个节点的孩子节点排列起来,看成是一个线性表,且以单链表作为存储结构,则n个节点有n个孩子链表(叶子节点的孩子链表为空表)。而n个头指针又组成一个线性表,为了便于查找,可采用顺序存储结构。这种存储结构可形式地说明如下:

#define MAX_TREE_SIZE 100

typedef struct CTNode{    //孩子节点

    int child;

    struct CTNode *next;

}*ChildPtr;

typedef struct{

    TElemType data;

    ChildPtr firstchild;    //孩子链表头指针

}CTBox;

typedef struct{

    CTBox nodes[ MAX_TREE_SIZE ];

    int n,r;    //节点数和根节点位置

}CTree;

  

  与双亲表示法相反,孩子表示法便于那些涉及孩子操作的实现,却不适合用于PARENT(T,x)的操作。我们可以把双亲表示法和孩子表示法合起来,既将双亲表示和孩子链表和在一起。

  《3》、孩子兄弟表示法

  又称二叉树表示法,或二叉树表示法。既以二叉树表作树的存储结构。链表中节点的两个链域分别指向该节点的第一个孩子节点和下一个兄弟节点,分别命名为firstchild域和nextsibling域。存储结构形式说明如下:

#define MAX_TREE_SIZE 100

typedef struct CSNode{

    ElemType data;

    struct CSNode *firstchild,*nextsibling;

}CSNode,*CSTree;

  

  利用这种结构便于实现各种树的操作。

符合题目要求的结果

  采用了树存储结构中的《孩子表示法》,当然有些改进。

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<algorithm>

#define M 100100  //最大长度

using namespace std;

//孩子节点结构

typedef struct Node

{

        int vex;

        Node* next;

}Child;

Child* head[M];//链表头数组

int f[M][2],pow[M],visit[M];

//pow[]权值数组,p[i]表示第i个节点的权值
//f[i][1]保留节点i时最大权值,f[i][0]不保留节点i时的最大权值
//visit[i]==1表示i点被访问过,visit[i]==0表示节点i未被访问过

//添加边(对称的)
void addADJ(int u,int v)

{

        Child *p,*q;

        p=(Child*)malloc(sizeof(Child));

        p->vex=v;

        p->next=head[u];

        head[u]=p;

        q=(Child*)malloc(sizeof(Child));

        q->vex=u;

        q->next=head[v];

        head[v]=q;

}

//动态规划获取结果

void GetResul(int v)

{

        visit[v]=1;

        Child *p;

        for(p=head[v];p!=NULL;p=p->next)

        {

                if(visit[p->vex]==0)

                {

                         GetResul(p->vex);

                         f[v][1] = f[v][1]+f[p->vex][0];

                         f[v][0]+=max(f[p->vex][0],f[p->vex][1]);

                }

        }       

        f[v][1]+=pow[v];

}

int main()

{

        int i,j,u,v,n;

        memset(head,NULL,sizeof(head));

        memset(f,0,sizeof(f));

        memset(visit,0,sizeof(visit));

        scanf("%d",&n);

        for(i=1;i<=n;i++)

        {

                scanf("%d",&pow[i]);

        }

        for(i=1;i<n;i++)

        {

                scanf("%d%d",&u,&v);

                addADJ(u,v);

        }

        GetResul(1);//从节点1开始进行动态规划

        printf("%d\n",max(f[1][0],f[1][1]));//结果输出

        return 0;

}
时间: 2024-10-24 21:53:30

树形动态规划练习《蓝桥杯 结点选择》的相关文章

蓝桥杯:结点选择

问题描述有一棵 n 个节点的树,树上每个节点都有一个正整数权值.如果一个点被选择了,那么在树上和它相邻的点都不能被选择.求选出的点的权值和最大是多少? 输入格式第一行包含一个整数 n .接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值.接下来一共 n-1 行,每行描述树上的一条边. 输出格式输出一个整数,代表选出的点的权值和的最大值. 样例输入51 2 3 4 51 21 32 42 5 样例输出12 样例说明选择3.4.5号点,权值和为 3+4+5 = 12 . 数据规模与约

蓝桥杯 节点选择 树状动态规划

算法训练 结点选择 时间限制:1.0s   内存限制:256.0MB 锦囊1 使用树型动态规划. 锦囊2 用F[i]表示从子树i中选择结点,且结点i必须被选择的最大值,用G[i]表示从子树i中选择结点,且结点i必须不被选择的最大值. 则F[i]=a[i]+\sum(G[j]),其中a[i]表示结点i的权值,j是i的子结点. G[i]=\sum(max(F[j], G[j])),其中j是i的子结点. 问题描述 有一棵 n 个节点的树,树上每个节点都有一个正整数权值.如果一个点被选择了,那么在树上和

算法笔记_076:蓝桥杯练习 结点选择(Java)

目录 1 问题描述 2 解决方案   1 问题描述 问题描述 有一棵 n 个节点的树,树上每个节点都有一个正整数权值.如果一个点被选择了,那么在树上和它相邻的点都不能被选择.求选出的点的权值和最大是多少? 输入格式 第一行包含一个整数 n . 接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值. 接下来一共 n-1 行,每行描述树上的一条边. 输出格式 输出一个整数,代表选出的点的权值和的最大值. 样例输入 51 2 3 4 51 21 32 42 5 样例输出 12 样例说明

蓝桥杯——说好的进阶之取数博弈游戏(动态规划实现)

今盒子里有n个小球,A.B两人轮流从盒中取球,每个人都可以看到另一个人取了多少个,也可以看到盒中还剩下多少个,并且两人都很聪明,不会做出错误的判断. 我们约定: 每个人从盒子中取出的球的数目必须是:1,3,7或者8个. 轮到某一方取球时不能弃权! A先取球,然后双方交替取球,直到取完. 被迫拿到最后一个球的一方为负方(输方) 请编程确定出在双方都不判断失误的情况下,对于特定的初始球数,A是否能赢? 程序运行时,从标准输入获得数据,其格式如下: 先是一个整数n(n<100),表示接下来有n个整数.

算法训练 结点选择 【树形dp】

算法训练 结点选择 时间限制:1.0s   内存限制:256.0MB 问题描述 有一棵 n 个节点的树,树上每个节点都有一个正整数权值.如果一个点被选择了,那么在树上和它相邻的点都不能被选择.求选出的点的权值和最大是多少? 输入格式 第一行包含一个整数 n . 接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值. 接下来一共 n-1 行,每行描述树上的一条边. 输出格式 输出一个整数,代表选出的点的权值和的最大值. 样例输入 5 1 2 3 4 5 1 2 1 3 2 4 2 5

蓝桥杯练习系统题解

转于:http://www.cnblogs.com/cshhr/p/3550014.html 蓝桥杯官网练习系统题解(非VIP) BEGIN-4(Fibonacci数列) 有递推公式,大家都知道用递推公式求,仅仅要记得在递推的时候同一时候取模求好 这里给一份另类代码,用矩阵高速幂求,事实上还有循环节 /* (1 1) * (Fn-1) = ( Fn )//矩阵相乘,将就着看吧 (1 0) (Fn-2) (Fn-1) (1 1) * (1 1) * (Fn-2) = ( Fn ) (1 0) (1

2015年第六届蓝桥杯C/C++B组省赛题目解析

一.奖券数目 有些人很迷信数字,比如带“4”的数字,认为和“死”谐音,就觉得不吉利.虽然这些说法纯属无稽之谈,但有时还要迎合大众的需求.某抽奖活动的奖券号码是5位数(10000-99999),要求其中不要出现带“4”的号码,主办单位请你计算一下,如果任何两张奖券不重号,最多可发出奖券多少张. 请提交该数字(一个整数),不要写任何多余的内容或说明性文字. 分析:直接枚举10000-99999之间的数字,如果带4,直接排除:不带4的,记录一次,直到枚举完后输出. #include <iostream

2015年蓝桥杯省赛B组C/C++(试题+答案)

首先说,这次我是第二次参加蓝桥杯(大学里最后一次),可这次去连个三等都没拿到,有些心灰意冷,比上一次还差, 当时看到成绩出来的时候有些失落,但是跌倒了,再站起来继续跑就可以了.可能是状态不好吧,纯属自我安慰. 接下来我把今年的题目又重新做了一遍,写下了这篇博客,如果也有需要探讨答案的,希望可以有帮助. 第一题: 第1题:统计不含4的数字 题目大意 统计10000至99999中,不包含4的数值个数. 解题分析: 第一种解法: 数学方法,这种是在网上看到的一种解法: 最高位除了0.4不能使用,其余8

第六届蓝桥杯本科B组C++省赛题解

比赛结束已经一星期了,成绩也出来了,江苏非211组的省前十,但是深感自己还是有太多的不足.绝对不能以自己还只是大一为借口,acm这条路还长的很. 目测得了95分(满分150),第一题错了,代码填空第一题错了,倒数第二题扣了一点分,最后一道大题全错. 之所以会这么晚来发这道题解,是因为深感自己不足,倒数第二题之所以没有做出来,是因为自己居然不会用[矩阵快速幂].因此,现学现用以自省. 关于题目:所有填空题都可以纯暴力,只要会回溯剪枝法对于蓝桥杯已经足够了.大题目难度一年比一年高 第一题 结果填空