https://www.luogu.com.cn/problem/P1352

题目描述

某大学有N个职员,编号为1~N。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri,但是呢,如果某个职员的直接上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。

输入格式

第一行一个整数N。(1<=N<=6000)

接下来N行,第i+1行表示i号职员的快乐指数Ri。(-128<=Ri<=127)

接下来N-1行,每行输入一对整数L,K。表示K是L的直接上司。

最后一行输入0 0

输出格式

输出最大的快乐指数。

输入输出样例

输入 #1复制

7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
0 0
输出 #1复制

5样例分析:

根节点为5,那么3,4不去,就可以获得最大快乐值=5

思路:可用树形DP或者拓扑排序来做一开始想到的可能是用一个一维数组dp[i]表示在第i个人的位置能获得的最大快乐,但是这个位置上的人去或者不去,都会对下属有影响,具有后效性,
比如说dp[2]作为根节点,那么他的最优解肯定是这个节点的快乐值,如果3要参加,则dp[3]无法继承dp[2]的最优解

那么我们可以在这个基础上增加一维,用来1/0表示这个人去还是不去如果不去,那么他的直接下属都可以去,dp[i][0]=sum((max(dp[son][1],dp[son][0])) son表示员工若去,则他的直接下属都不能去,dp[i][1]=sum(dp[son][0]);
 

那什么是后效性?

无后效性,有两层含义。

第一层含义是,在推导后面阶段状态的时候,我们只关心前面阶段的状态值,不关心这个状态是怎么一步步推导出来的。

第二层含义是,某阶段状态一旦确定,就不受之后阶段的决策影响。无后效性是一个非常“宽松”的要求。只要满足前面提到的动态规划问题模型,其实基本上都会满足无后效性。

---来源CSDN博客

简单些的例子:迷宫问题中,假设你走到了(n,m)点,之后的状态转移不会再关心你是如何走到(n,m)点的,只关心你在(n,m)点的状态信息(例如耗费)。

之后发生的不会影响之前的结果。拿最长公共子序列来说,你在后面碰到的字符不会影响你前面字符的匹配数量和结果,每次增加匹配到的字符时,都是“继承”前面的结果之后加一。所以如果后面的字符如果能改变前面的字符,那么我们存状态意义就不大了。就是因为有大量重复计算在递归里,我们才用空间换时间,用了动态规划。如果状态总是变,那也没必要存了。每次都暴力算就行。---来源知乎某处(我忘了)

所以我们需要在这个基础上再加上一维,分别以0和1表示这个人去或者不去。
 #include<iostream>
 #include<cstring>
 #include<math.h>
 #include<stdlib.h>
 #include<cstring>
 #include<cstdio>
 #include<utility>
 #include<algorithm>
 #include<queue>
 using namespace std;
 typedef long long ll;
 inline int read(){
     ,w=;;
     while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
     )+(X<<)+(ch^),ch=getchar();
     return w?-X:X;
 }
 /*------------------------------------------------------------------------*/
 ;
 ],fa[maxn];
 vector<int>son[maxn];
 ;
 void bfs(int root){

     queue<int>q;
     q.push(root);
     vis[root]=;
     tree[++cnt]=root;

     while(!q.empty()){

         int now=q.front();q.pop();

         int len=son[now].size();

         ;i<len;++i){

             if(!vis[son[now][i]]){
                 vis[son[now][i]]=;
                 tree[++cnt]=son[now][i];
                 q.push(son[now][i]);
             }

         }

     }

 }
 int main( )
 {
     ios_base::sync_with_stdio(); cin.tie(); cout.tie();
     //freopen("a.txt","r",stdin);
     //freopen("a.txt","w",stdout);
     int n;
     cin>>n;
     ;i<n+;++i){
         fa[i]=i;
         cin>>v[i];

     }
     ;i<=n;++i){
         int u,v;
         cin>>u>>v;
         )break;

         fa[u]=v;
         son[v].push_back(u);

     }

     int root = n;

     while(fa[root]!=root)root=fa[root];

     bfs(root);

     //从叶子节点开始dp
     ;--i){

         int now=tree[i];
         int len=son[now].size();

         ;j<len;++j){//他的某一个下属 

             //1表示去
             dp[now][]+=max(dp[son[now][j]][],dp[son[now][j]][]);
             dp[now][]+=dp[son[now][j]][]; //如果now去,则now的下属不能去
         }

         dp[now][]+=v[now];
         //根节点,写在外面的原因是
         //叶子节点无法进入第二层循环
         //并且叶子节点表示now去,所以二维状态是1 

     }

     cout<<(max(dp[root][],dp[root][]))<<endl;

     ;
 }
拓扑排序的DP思想和上面的差不多,就是省略了建树的过程,因为拓扑排序的算法特殊性帮助我们完成了这一过程,根节点入度为0,那么我们就可以不断地在排序过程中完成DP
 #include<iostream>
 #include<cstring>
 #include<math.h>
 #include<stdlib.h>
 #include<cstring>
 #include<cstdio>
 #include<utility>
 #include<algorithm>
 #include<queue>
 #include<vector>
 #include<map>
 using namespace std;
 ;
 ];
 int n;

 int main(){

     cin>>n;

     ;i<=n;++i){
         cin>>a[i];
     }
     vector<int>son[maxn];
     int u,v;
     while(cin>>u>>v&&u&&v){

         du[v]++;

         son[u].push_back(v);
     }

     queue<int>q;
     ;i<=n;++i){

         if(!du[i]){//叶子节点
             q.push(i);
             dp[i][]=a[i];
         }
     }
     map<int,int>mp;
     ;
     while(!q.empty()){

         int now=q.front();q.pop();
         int len=son[now].size();

         ;i<len;++i){

             int boss=son[now][i];
             dp[boss][]+=max(dp[now][],dp[now][]);
             dp[boss][]+=dp[now][];

             du[boss]--;
             if(!du[boss]){
                 dp[boss][]+=a[boss];
                 q.push(boss);
                 mp[i]=;
             }
             ans=max(dp[boss][],dp[boss][]);
         }

     }
     cout<<ans<<endl;
     ;
 } 

发现我这个代码写的有点复杂。。。

参考了一位大佬的代码,和我的代码的区别是我用vector存每个节点的上司,那么判断过程中就需要每次从vector里面取出,其实我们只需要设置一个father数组用来存储就可以了

 #include<iostream>
 #include<cstring>
 #include<math.h>
 #include<stdlib.h>
 #include<cstring>
 #include<cstdio>
 #include<utility>
 #include<algorithm>
 #include<queue>
 using namespace std;
 typedef long long ll;
 inline int read(){
     ,w=;;
     while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
     )+(X<<)+(ch^),ch=getchar();
     return w?-X:X;
 }
 /*------------------------------------------------------------------------*/
 ;
 ],father[maxn],du[maxn];
 ;
 int main( )
 {
     ios_base::sync_with_stdio(); cin.tie(); cout.tie();
     //freopen("a.txt","r",stdin);
     //freopen("a.txt","w",stdout);

     int n;
     cin>>n;
     ;i<n+;++i){
         father[i]=i;
         cin>>v[i];

     }
     ;i<=n;++i){
         int u,v;
         cin>>u>>v;
         )break;

         father[u]=v;

         du[v]++;

     }
     queue<int>q;
     ;i<=n;++i){
         if(!du[i]){

             q.push(i);
         }

     }
     int root;
     while(!q.empty()){

         int now=q.front();q.pop();
         root=now;
         //上司不去
         dp[father[now]][]=max(dp[now][],dp[now][]);
         //去
         dp[father[now]][]+=v[father[now]]+dp[now][]; 

         du[father[now]]--;
         if(!du[father[now]]){
             q.push(father[now]);
         }
     }
     cout<<max(dp[root][],dp[root][])<<endl;

     ;
 }

P1352 没有上司的舞会&&树形DP入门的更多相关文章

  1. P1352 没有上司的舞会——树形DP入门

    P1352 没有上司的舞会 题目描述 某大学有N个职员,编号为1~N.他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司.现在有个周年庆宴会,宴会每邀请来一个职员 ...

  2. 洛谷P1352 没有上司的舞会——树形DP

    第一次自己写树形DP的题,发个博客纪念`- 题目来源:P1352 没有上司的舞会 题目描述 某大学有N个职员,编号为1~N.他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结 ...

  3. P1352 没有上司的舞会[树形dp]

    题目描述 某大学有N个职员,编号为1~N.他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司.现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri, ...

  4. [luogu]P1352 没有上司的舞会[树形DP]

    本Lowbee第一次写树形DP啊,弱...一个变量写错半天没看出来...... 题目描述 某大学有N个职员,编号为1~N.他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点 ...

  5. 洛谷 P1352 没有上司的舞会 树形DP板子

    luogu传送门 题目描述: 某大学有n个职员,编号为1~n. 他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司. 现在有个周年庆宴会,宴会每邀请来一个职员都会 ...

  6. 洛谷 P1352 没有上司的舞会(树形 DP)

    题目描述 某大学有N个职员,编号为1~N.他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司.现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri, ...

  7. 『没有上司的舞会 树形DP』

    树形DP入门 有些时候,我们需要在树形结构上进行动态规划来求解最优解. 例如,给定一颗\(N\)个节点的树(通常是无根树,即有\(N-1\)条无向边),我们可以选择任意节点作为根节点从而定义出每一颗子 ...

  8. CodeVS1380 没有上司的舞会 [树形DP]

    题目传送门 没有上司的舞会 题目描述 Description Ural大学有N个职员,编号为1~N.他们有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司.每个职员有一个 ...

  9. 没有上司的舞会 树形dp

    题目描述 某大学有N个职员,编号为1~N.他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司.现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri, ...

随机推荐

  1. abp(net core)+easyui+efcore实现仓储管理系统——入库管理之五(四十一)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

  2. 安装SQL Server 2008R2 报错“此计算机上安装了 Microsoft Visual Studio 2008 的早期版本”解决方法

    安装SQL Server 2008 R2报错“此计算机上安装了 Microsoft Visual Studio 2008 的早期版本,请在安装 SQL Server 2008 前将 VS2008 升级 ...

  3. Android RadioGroup+Fragment 底部导航

  4. 贵州省网络安全知识竞赛团体赛Writeup-phpweb部分

    0x01 混淆后门#conn.php 首先还是拖到D盾扫描 打开conn.php发现底部有那么一串代码: 对这个代码进行分析 首先可以对几个比较简单的变量输出看一下 $s输出内容为create_fun ...

  5. no parameterless constructor define for type 解决一例

    在生成根据模型和上下文生成带增删查改操作的视图的控制器时,提示上述信息,网上查找了资料也没有解决,突然想起该项目是连接MSSQL数据库和Redis数据库的,并且已经依赖注入了,而Redis数据库的服务 ...

  6. Redis 主从复制技术原理

    基于前面介绍的 Redis 内容,Redis 只能作为一个单机内存数据库,一旦服务器宕机即不能提供服务,即便能通过持久化策略重启恢复数据,往往也做不到百分之百还原.再一个就是,单机的 Redis 需要 ...

  7. 2020年PHP 面试问题(三)

    2020年PHP 面试问题(一) 2020年PHP 面试问题(二) 一.数据库三范式 第一范式:1NF是对属性的原子性约束,要求属性具有原子性,不可再分解: 第二范式:2NF是对记录的惟一性约束,要求 ...

  8. POS-商户手续费-从生活剖析,通俗易懂

    前言 我们大家日常都在使用pos机,尤其是买衣服,吃火锅,都习惯刷卡.pos机带来的消费便利,也正是市场 对经济的一种促进手段. 今天主要分享下商户手续费这个概念,引出这个概念前,我们先大概说点别的东 ...

  9. 深度学习中正则化技术概述(附Python代码)

    欢迎大家关注我们的网站和系列教程:http://www.tensorflownews.com/,学习更多的机器学习.深度学习的知识! 磐石 介绍 数据科学研究者们最常遇见的问题之一就是怎样避免过拟合. ...

  10. 从使用到原理,探究Java线程池

    什么是线程池 当我们需要处理某个任务的时候,可以新创建一个线程,让线程去执行任务.线程池的字面意思就是存放线程的池子,当我们需要处理某个任务的时候,可以从线程池里取出一条线程去执行. 为什么需要线程池 ...