树形dp基础
今天来给大家讲一下数形dp基础
树形dp常与树上问题(lca、直径、重心)结合起来
而这里只讲最最基础的树上dp
1.选课
题目描述
在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 N 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程a是课程b的先修课即只有学完了课程a才能学习课程b)。一个学生要从这些课程里选择 M 门课程学习,问他能获得的最大学分是多少?思路:
我们可以把两门课先后关联关系想象成树边,然后这一切即可构成一棵树,因为每个结点只有一个父亲符合树性质。
所以每个结点能选的基础,便是父节点能选,他才能选。
状态:dp[u][j]表示以u为根的子树里面选择j门课程的最大得分。
决策:选or不选
转移:(若u->v)
dp[u][j]=dp[v][k]+dp[u][j-k];
这里运用了 多重背包+dp 的思想:
每一个v所在的子树都有它的价值,你要枚举他们每个的第二维度(可以看成体积),从而更新。
代码可以仿照下一题
2.吊灯
- 题目描述
XZL买了一批吊灯,可发现并不能直接把这吊灯挂起来:只有一个吊灯能挂在天花板上,而其他所有的灯只能固定的挂在某一个别的吊灯下。每个吊灯都有其本身的重量,也有一定的承受能力(指下面最多能承受的重力),每个吊灯也有不同的亮度。XZL希望能够选出其中的一些吊灯吊起来,每个灯下面所吊的都在其重力承受范围之内,且使所有灯的亮度之和最大。 - 思路 这道题和上一题,几乎一样,代码很短:
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
const int N=405;
int w[N],f[N][N],ud[N],ad[N],nxt[N],head[N],to[N],tot,n;
void add_edge(int u,int v) {
nxt[++tot]=head[u]; to[tot]=v; head[u]=tot;
}
void dfs(int u) {
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];
dfs(v);
for(int j=ud[u];j>=w[v];j--) {
for(int k=0;k<=min(j-w[v],ud[v]);k++) { //注意这里顺序枚举,因为下面的f[u][j-k-w[v]]要保证倒序(01or多重背包的无后效性)
f[u][j]=max(f[u][j],f[u][j-k-w[v]]+f[v][k]+ad[v]);
}
}
}
}
int main() {
int rt;
scanf("%d",&n);
for(int i=1;i<=n;i++) {
int u;
scanf("%d%d%d%d",&u,&w[i],&ud[i],&ad[i]);
if(!u) rt=i;
else add_edge(u,i);
}
dfs(rt);
int ans=0;
for(int i=0;i<=ud[rt];i++) {
ans=max(ans,f[rt][i]);
}
printf("%d",ad[rt]+ans);
return 0;
}
2.警卫安排
- 题目描述
一条边的两端必需至少一个警卫,安排每个警卫都有其自身的代价,这样安排警卫的最少代价。 - 思路
状态:
f[i][0]为当前节点安排警卫1
f[i][1]为当前节点的儿子中有安排警卫的
f[i][2]为当前节点的父亲安排了警卫
f[i][2]也时必不可少的,因为f[i][1]和f[i][2]的转移是不同的。
因此代码如下:
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
const int N=725;
int w[N],fa[N],rt;
int tot,nxt[N*2],to[N*2],head[N],f[N][3];
void add_edge(int u,int v) {
nxt[++tot]=head[u]; to[tot]=v; head[u]=tot;
}
void dfs(int u){
if(!head[u]) {
f[u][0]=w[u]; f[u][1]=0x3f3f3f3f; f[u][2]=0; return;
}
bool flag=false;
int cha=0x3f3f3f3f;
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];
dfs(v);
f[u][0]+=min(min(f[v][1],f[v][2]),f[v][0]);
f[u][2]+=min(f[v][1],f[v][0]);
f[u][1]=f[u][2];
if(f[v][0]<f[v][1]) flag=true;
cha=min(cha,f[v][0]-f[v][1]);
}
f[u][0]+=w[u];
if(!flag) {
f[u][1]+=cha;
}
}
int main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) {
int u,v,m,k;
scanf("%d%d%d",&u,&k,&m);
w[u]=k;
for(int j=1;j<=m;j++) {
scanf("%d",&v);
add_edge(u,v); fa[v]=u;
}
}
for(int i=1;i<=n;i++) {
if(!fa[i]) rt=i;
}
dfs(rt);
printf("%d",min(f[rt][0],f[rt][1]));
return 0;
}
3.没有上司的晚会
- 题目描述
每个人的上司若来了,这个人就不会来,每个人都有一个气氛值,求最大总气氛值。 - 思路
跟上一题,进行比较,区别为:
1.最小代价 vs. 最大总价值
2.对于一条边,上题是至少要有一个结点,而此题是最多有一个结点
3.【总结1.2】此题的u->v转移跟fa[u]无关,只跟u取或不取有关,而上题要考虑若fa[u]没取,且u没取,{v}中必须有取的情况。
代码:
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
const int N=6005;
int tot,head[N],fa[N],nxt[N],to[N],a[N];
int dp[N][2];
void add_edge(int u,int v) {
tot++; nxt[tot]=head[u]; to[tot]=v; head[u]=tot;
}
void dfs(int u) {
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];
dfs(v);
dp[u][0]+=max(dp[v][1],dp[v][0]);
dp[u][1]+=dp[v][0];
}
dp[u][1]+=a[u];
}
int main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
}
int u,v;
while(1) {
scanf("%d%d",&v,&u);
if(!v&&!u) break;
fa[v]=u;
add_edge(u,v);
}
int ans=0;
for(int i=1;i<=n;i++) {
if(!fa[i]) {
dfs(i);
ans+=max(dp[i][0],dp[i][1]);
}
}
printf("%d",ans);
return 0;
}
4.清洁机器人
- 题目描述
现在有k台清洁机器人位于s号教室,现在有k台清洁机器人位于s号教室。机器人靠燃油驱动,一台机器人清洁一个教室的耗油1升。在道路上行走时,每单位距离耗油1L。我们希望完成清洁作业消耗的总油量尽可能少。请你计算出这个总油量。作业结束时,机器人可以停留在任何位置。 - 思路
思考问题,从浅入深。
首先此题有一个棘手的条件:结束时,机器人可以停留在任何位置。
假设情况1:只有一个机器人,并且结束时必须回到原点,那么ans1=所有len*2的和
延申:如果一个机器人,结束时不用回到原点,那么ans2=ans1-max{一条到叶子节点的链}
所以状态为:f[u][j]表示u子树中清扫完后,有j个机器人留在了u中。
方程为:
k>0dp[u][j]=min{dp[u][j],dp[u][j-k]+dp[v][k]+k*len[i]};//这k个点去了不回,每个点只统计一次去的len[i]
k=0dp[u][j]=dp[v][0]+2*len[u][v];//v子树没有点留下,而这条边则要贡献两次了【既然v子树没有点,那就像情况1一样递推】 - 代码
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
typedef long long ll;
int tot,nxt[N*2],head[N],to[N*2],K;
ll len[N*2],dp[N][15],inf=1e18;
void add_edge(int u,int v,ll w) {
++tot; nxt[tot]=head[u]; to[tot]=v; len[tot]=w; head[u]=tot;
}
void dfs(int u,int fa) {
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];
if(v==fa) continue;
dfs(v,u);
for(int j=K;j>=0;j--) {
//每个子节点都必须选的背包,(否则下一行是取min)
dp[u][j]=dp[u][j]+dp[v][0]+2*len[i];
for(int k=1;k<=j;k++) {
dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]+k*len[i]);
}
}
}
}
int main() {
int n,s;
scanf("%d%d%d",&n,&s,&K);
for(int i=1;i<n;i++) {
int u,v;
ll w;
scanf("%d%d%lld",&u,&v,&w);
add_edge(u,v,w);
add_edge(v,u,w);
}
dfs(s,0);
ll ans=inf;
for(int i=0;i<=K;i++) {
ans=min(ans,dp[s][i]);
}
printf("%lld",ans+n);
return 0;
}
树形dp基础的更多相关文章
- hdu 1561 The more, The Better(树形dp,基础)
The more, The Better Time Limit: 6000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Oth ...
- hdu 1520 树形DP基础
http://acm.hdu.edu.cn/showproblem.php?pid=1520 父节点和子节点不能同时选. http://blog.csdn.net/woshi250hua/articl ...
- poj2342 没有上司的舞会 树形dp基础
#include<iostream> #include<cstring> #include<cstdio> #include<vector> using ...
- HDU1520 Anniversary party 树形DP基础
There is going to be a party to celebrate the 80-th Anniversary of the Ural State University. The Un ...
- POJ 2342 Anniversary party 树形DP基础题
题目链接:http://poj.org/problem?id=2342 题目大意:在一个公司中,每个职员有一个快乐值ai,现在要开一个party,邀请了一个员工就不可能邀请其直属上司,同理邀请了一个人 ...
- 树形DP基础题 HDU1520
Anniversary party Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others ...
- 提升——树形DP
这里讲提高一点的内容,所以没有树形DP基础的,先看一下基础部分: 浅说——树形DP 闲言不表,看第一题. 这道题是典型的树上最长链问题.(就是一个模板题) 给定一棵树,树上共有N个节点(N<=5 ...
- hdu 1054 Strategic Game (简单树形DP)
Strategic Game Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) ...
- 几个树形dp
1.重建道路 树形dp基础题,f[i][j]表示在i这个点我和我的子树联通块大小为j最少砍几条边. 转移的时候,到下一个子树时上一个子树所有答案先++(此树直接砍掉不贡献答案),再继续dp. 注意更新 ...
随机推荐
- 【每日日报】第三十二天---DataOutputStream写文件
1 今天继续看书 DataOutputStream写文件 1 package File; 2 import java.io.IOException; 3 import java.io.FileOutp ...
- C语言-操作符与表达式
C语言入门之操作符与表达式 前言 本篇文章主要包括各种操作符的介绍与表达式求值,欢迎各位小伙伴与我一起学习. 一.操作符 分类 算术操作符 移位操作符 位操作符 赋值操作符 单目运算符 关系操作符 逻 ...
- 进程的概念及multiprocess模块的使用
一.进程 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在早期面向进程设计的计算机结构中,进程是程序的基本执行实体:在 ...
- Java学习day16
IO流即输入/输出流,按数据类型分为:字节流和字符流 与IO有关的操作最后都要释放,使用close方法 以字节流形式写入数据后需要换行可以添加换行符,注意旧版系统之间识别的换行符不相同,旧版Windo ...
- Spring Security实现基于RBAC的权限表达式动态访问控制
昨天有个粉丝加了我,问我如何实现类似shiro的资源权限表达式的访问控制.我以前有一个小框架用的就是shiro,权限控制就用了资源权限表达式,所以这个东西对我不陌生,但是在Spring Securit ...
- GO语言学习——Go语言基础之流程控制一
Go语言基础之流程控制 if else(分支结构) package main import "fmt" // if条件判断 func main(){ // age := 19 // ...
- BUUCTF-MISC:二维码
题目 解题过程 1.点击下载附件,发现是一个压缩包,解压后得到一张二维码 2.使用QR research扫描,得到的内容并不是flag 3.使用010editor打开图片分析,发现图片里面含有一个tx ...
- 使用allatori混淆代码
引言 为了保密需要,我们开发出来的程序在实际部署之前,需要先将jar包的代码进行混淆,让用户使用反编译也无法获得源码. allatori就是这样一个混淆java jar包代码的工具. 操作步骤 Ste ...
- 团队Arpha1
队名:观光队 组长博客 作业博客 组员实践情况 王耀鑫 **过去两天完成了哪些任务 ** 文字/口头描述 完成服务器连接数据库部分代码 展示GitHub当日代码/文档签入记录 接下来的计划 与服务器连 ...
- Linux 30岁,这些年经历了什么?
关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 3月19日,Linux基金会在Twitter上发布推文宣布,其小企鹅的标志"Tux& ...