宝藏(树形DP)
这道题目是十分考验思维的,n^2应该还是比较好想的,主要是如何转移根的问题。转移根,在我看来应该是树形dp最难的一部分了,
一般学会如何转移根,也就差不多考验通吃树形dp了。
下面转一转大佬链接:http://blog.csdn.net/FromATP/article/details/53200240
题解
比较好想的树形DP。。但是实现起来很多地方不是一般恶心。。。也许是ATP的方法太愚蠢了?总之好像当时考试的时候ATP最后半个小时想出来了但是没码完只好写了50pts部分分= =醉死了
那就先从50pts的开始说起吧。。可以想到这个数据范围允许我们把每个节点当做根每次都重新进行dfs,那就用一个f[i][0]表示从i号节点出发到它的子树里去转一圈最后还要回来的最大收益,f[i][1]表示从i号节点出发到它的子树里转一圈最后不回来的最大收益。那么分别以每个点u为根dfs的时候最大收益就是f[u][0]和f[u][1]的Max。
但是100pts的话肯定不能每次重新dfs,就要考虑一遍dfs维护出所有需要的信息。因为它要的是一定经过某一个点的最大收益路线,一遍dfs的话只能维护出从这个点出发到它的子树里走一圈的最大收益,于是再维护一个g[i][0]数组代表从i这个点出去到它的子树外面走一圈并且回来的最大收益,g[i][1]表示从这个点出去走一圈并且回来的最大收益。但是注意的情况是递推g数组的时候因为要分成在它父亲外面走一圈和在它兄弟节点里走一圈两种情况,所以搞它的兄弟节点的时候不能包括它本身。这个需要注意。
那么到底如何来维护这两个数组呢?对于f[i][0],反正它每次走出去都要回来,所以就一个子树一个子树地考虑。对于当前子树v,f[v][0]是已知的,那么跑到这个子树里面走一圈再回来的收益就可以算出来。如果这个收益大于0,显然选上v这个子树肯定会让解变得更优。也就是说f[i][0]是贪心维护的,枚举每一棵子树的时候只要它的收益大于0就一定要选。
对于f[i][1],它需要在i的所有子树中选一棵子树,当跑到这棵子树里面的时候它就不再回到点i了。除了这棵子树以外的其它子树还是可以用f[v][0]来贪心选择的。那么在递推f[i][1]的时候每次枚举到一个子树v,显然目前的f[i][1]里面存储的结果对应的那棵不回来的子树在v前面。于是就要分成仍然让v之前的那棵子树不回来和让v不回来两种情况讨论。如果仍然让v之前的那棵子树不回来,v就要回来,于是就用f[v][0]贪心地更新;否则因为v要回来,所以v之前所有的都不能回来,就用当前的f[i][0]加上f[v][1]来更新结果——这就要求每次f[i][0]要在f[i][1]之后更新否则f[i][0]里面存的就不是v之前的子树而有可能包括v了,就造成了重复更新。
g数组要求自顶向下而不是自底向上递推,因为只有知道了父亲的情况才能递推儿子。对于g[i][0],它是跑到外面转一圈再回来的最大收益,于是首先继承g[fa][0]的结果,表示它到它父亲节点的外面转了一圈再回来;然后还有它经过父亲到兄弟节点里面转一圈回来的情况,这个肯定不能枚举现算啊对吧,所以我们考虑利用之前的结果。注意到f[fa][0]就已经包括了i的所有兄弟节点的信息,但是问题就在于有可能也同时包括了i,这是不合法的。所以我们要判断一下i有没有对f[fa][0]产生贡献,如果有的话要减去。因为是贪心选择的所以也很好判断。
最后是g[i][1]。需要分成不回来的那条路在它父亲外面还是在它兄弟节点里面两部分讨论。如果不回来的那条路在父亲外面的话就是用g[fa][1]加上它到兄弟节点里转一圈然后回来的收益,这个和上面g[i][0]计算的时候是一样的。关键就是不回来的那条路再兄弟节点里面的这种情况比较麻烦。不能直接用f[fa][1]啥的来更新,因为在计算f[fa][1]的时候所使用的不回来的那个儿子可能本身就是当前要计算的i。也就是用于递推i的那个东西必须保证它不回来的那个儿子必须在i外面。所以在递推f数组的时候又循环了一遍,一开始递推f[u][1]的时候记录了一下它不回来的儿子是哪一个,然后再强制让这个儿子一定拐回来,递推一个f[u][2]。这样在递推g[i][1]的时候,f[fa][1]和f[fa][2]一定至少有一个它选择的不回来的路径是在i外面的,就用那个来更新。更新的时候仍然记得减去可能存在的f[i][0]的贡献。
虽然十分长,但是十分详细,十分~\(≧▽≦)/~。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,p[],a[],nxt[],tot;
long long f[][],g[][],val[],w[],rec[];
void add(int x,int y,long long v){
tot++;a[tot]=y;nxt[tot]=p[x];w[tot]=v;p[x]=tot;
}
void dfs(int u,int fa){
int tmp=val[u],e;
f[u][]=f[u][]=f[u][]=val[u];//0:back;1:not back
for (int i=p[u];i!=;i=nxt[i])
if (a[i]!=fa){
int now,last;
dfs(a[i],u);
last=f[u][]+max((long long),f[a[i]][]-*w[i]);
now=f[u][]+max((long long),f[a[i]][]-w[i]);
if (now>last){
f[u][]=now;rec[u]=a[i];
}else f[u][]=last;
if (f[a[i]][]>=*w[i]) f[u][]+=f[a[i]][]-*w[i];
}
for (int i=p[u];i!=;i=nxt[i])
if (a[i]!=fa&&a[i]!=rec[u]){
int now,last;
last=f[u][]+max((long long),f[a[i]][]-*w[i]);
now=tmp+max((long long),f[a[i]][]-w[i]);
f[u][]=max(now,last);
if (f[a[i]][]>=*w[i]) tmp+=f[a[i]][]-*w[i];
}else if(a[i]==rec[u]) e=w[i];//记下最大值不回来节点的父边来更新
f[u][]=max(f[u][],f[u][]+f[rec[u]][]-*e);//最后要判断一下
}
void dfs_again(int u,int fa,int edge){
int son;
g[u][]=g[u][]=;
if (rec[fa]==u) son=f[fa][];
else son=f[fa][];//判断选择最大值还是次大值
son-=max(f[u][]-*edge,(long long));//减去当前子树的影响
g[u][]=max(g[u][],g[fa][]+son-edge);//停在兄弟节点里的情况
if (f[u][]>*edge) son=f[u][]-*edge;
else son=;//计算所有兄弟节点回来的价值和
son=f[fa][]-son;//停在父亲外面的情况
g[u][]=max(g[u][],son+g[fa][]-edge);
g[u][]=max(g[u][],son+g[fa][]-*edge);
for (int i=p[u];i!=;i=nxt[i])
if (a[i]!=fa) dfs_again(a[i],u,w[i]);
}
int main()
{
freopen("treasure.in","r",stdin);
freopen("treasure.out","w",stdout);
scanf("%d",&n);
for (int i=;i<n;i++){
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
add(x,y,w);add(y,x,w);
}
for (int i=;i<=n;i++) scanf("%d",&val[i]);
dfs(,);dfs_again(,,);
for (int i=;i<=n;i++)
printf("%I64d\n",max(f[i][]+g[i][],f[i][]+g[i][]));
return ;
}
附上了他的代码,我的代码在模拟赛中有写到。
宝藏(树形DP)的更多相关文章
- HDU 1561 The more, The Better【树形DP/有依赖的分组背包】
ACboy很喜欢玩一种战略游戏,在一个地图上,有N座城堡,每座城堡都有一定的宝物,在每次游戏中ACboy允许攻克M个城堡并获得里面的宝物.但由于地理位置原因,有些城堡不能直接攻克,要攻克这些城堡必须先 ...
- codevs1486愚蠢的矿工(树形dp)
1486 愚蠢的矿工 时间限制: 1 s 空间限制: 128000 KB 题目描述 Description Stupid 家族得知在HYC家的后花园里的中央花坛处,向北走3步,向西走3步, ...
- poj3417 LCA + 树形dp
Network Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 4478 Accepted: 1292 Descripti ...
- COGS 2532. [HZOI 2016]树之美 树形dp
可以发现这道题的数据范围有些奇怪,为毛n辣么大,而k只有10 我们从树形dp的角度来考虑这个问题. 如果我们设f[x][k]表示与x距离为k的点的数量,那么我们可以O(1)回答一个询问 可是这样的话d ...
- 【BZOJ-4726】Sabota? 树形DP
4726: [POI2017]Sabota? Time Limit: 20 Sec Memory Limit: 128 MBSec Special JudgeSubmit: 128 Solved ...
- 树形DP+DFS序+树状数组 HDOJ 5293 Tree chain problem(树链问题)
题目链接 题意: 有n个点的一棵树.其中树上有m条已知的链,每条链有一个权值.从中选出任意个不相交的链使得链的权值和最大. 思路: 树形DP.设dp[i]表示i的子树下的最优权值和,sum[i]表示不 ...
- 树形DP
切题ing!!!!! HDU 2196 Anniversary party 经典树形DP,以前写的太搓了,终于学会简单写法了.... #include <iostream> #inclu ...
- BZOJ 2286 消耗战 (虚树+树形DP)
给出一个n节点的无向树,每条边都有一个边权,给出m个询问,每个询问询问ki个点,问切掉一些边后使得这些顶点无法与顶点1连接.最少的边权和是多少.(n<=250000,sigma(ki)<= ...
- POJ2342 树形dp
原题:http://poj.org/problem?id=2342 树形dp入门题. 我们让dp[i][0]表示第i个人不去,dp[i][1]表示第i个人去 ,根据题意我们可以很容易的得到如下递推公式 ...
随机推荐
- C++学习日记(一)————类与对象
C++远征之封装篇(上)笔记 所有内容都是听课笔记,愿课堂视频如下: C++远征之封装篇(上)-慕课网http://www.imooc.com/learn/382 类和对象 1 什么是类,什么是对象 ...
- poj 3635 带花费的Dij+head优化
练习!! 这里主要需要注意的是进队的条件和dp[][]状态的控制,dp[i][j]表示到第i个城市剩余汽油为j的最小花费. 代码: #include<iostream> #include& ...
- ospf剩余笔记
OSPF 流程图: 带宽 开销 10 100 100 19 1000 4 10000 2 区域的划分减少lsdb的大小 有利于网络管理员故障排除 网络故障不会影响到其他区域 邻接关系 ...
- !Web云笔记--HTML基础
Web自学笔记第一阶段笔记综合汇总 参考资料:<Head First HTML&CSS>(中文第二版)(美国)弗里昂ISBN:9787508356464 中国电力出版社 全部阶段: ...
- 201521123052《Java程序设计》第6周学习总结
1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 注1:关键词与内容不求多,但概念之间的联系要清晰,内容覆盖 ...
- 201521123003《Java程序设计》第5周学习总结
1. 本章学习总结 1.1 尝试使用思维导图总结有关多态与接口的知识点. 参考资料: 百度脑图 XMind 2. 书面作业 Q1.代码阅读:Child压缩包内源代码 1.1 com.parent包中C ...
- Android四大组件(详细总结)
android四大组件分别为activity.service.content provider.broadcast receiver. 一.android四大组件详解 1.activity (1)一个 ...
- 201521123037 《Java程序设计》第9周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. java异常继承架构 2. 书面作业 本次PTA作业题集异常 1. 常用异常 题目5-1 1.1 截图你的提交结果( ...
- 201521123018 《Java程序设计》第11周学习总结
1. 本章学习总结 你对于本章知识的学习总结 2. 书面作业 一.互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问) 1.1 除了使用synchronized修饰方法实现互斥同步访问, ...
- 201521123048 《java程序设计》 第11周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线程 互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问) 1. ...