笔者已经很久没有打过题解了,如果打题解,就总是要连着一个知识点来打题解。

最近做过一共两道这样的题目。笔者认为这样的题有较强的可拓展性,比较有意义。

所以就打一篇博客。


问题概述

先说说这是个什么样的问题。

给你一棵树(无向),从某个点开始走,每到一个节点可以得到一些分数,如果这个点之前已经被走过,那就不能得到这些分数。每走一个边(或点)都会有一些代价。问在一定的代价内,最多的分数。

可能光是看文字不好理解题目大意,这里放道例题。

例题 JZOJ5819 大逃杀

Description

自从 Y 君退役之后,她就迷上了吃鸡,于是她决定出一道吃鸡的题。

Y 君将地图上的所有地点标号为 1 到 n,地图中有 n − 1 条双向道路连接这些点,通过一条 双向道路需要一定时间,保证从任意一个点可以通过道路到达地图上的所有点。

有些点上可能有资源,Y 君到达一个有资源的点后,可以选择获取资源来使自己的武力值增 加 wi,也可以选择不获取资源。如果 Y 君获取了一个点上的资源,这个点上的资源就会消失,获 取资源不需要时间。

有些点上可能有敌人,Y 君到达一个有敌人的点后,必须花费 ti 秒伏地与敌人周旋,并最终 将敌人消灭。如果 Y 君消灭了一个点上的敌人,这个点上的敌人就会消失。Y 君不能无视敌人继 续前进,因为这样会被敌人攻击。

如果一个点上既有资源又有敌人,Y 君必须先消灭敌人之后才能获取资源,否则就会被敌人 突袭。

游戏开始时,Y 君可以空降到任意一个点上,接下来,她有 T 秒进行行动,T 秒后她就必须 前往中心区域送快递。Y 君希望她前往中心区域送快递时,武力值尽可能大,请你帮助 Y 君设计 路线,以满足她的要求。你只需输出 T 秒后 Y 君的武力值。

Input

第一行由单个空格隔开的两个正整数 n, T,代表点数和时间。

第二行 n 个由单个空格隔开的非负整数代表 wi,如果 wi = 0 代表该点没有武器,

第三行 n 个由单个空格隔开的非负整数代表 ti,如果 ti = 0 代表该点没有敌人。

接下来 n − 1 行每行由单个空格隔开的 3 个非负整数 a, b, c 代表连接 a 和 b 的双向道路,通 过这条道路需要 c 秒。

Output

输出一行一个整数代表 T 秒后 Y 君的武力值。

Sample Input

17 54

5 5 1 1 1 25 1 10 15 3 6 6 66 4 4 4 4

0 1 3 0 0 0 1 3 2 0 6 7 54 0 0 0 0

1 8 3

2 8 3

8 7 7

7 13 0

7 14 0

15 14 2

16 14 3

17 14 5

7 9 4

9 10 25

10 11 0

10 12 0

7 6 20

3 6 3

3 4 3

3 5 3

Sample Output

68

Data Constraint

题目的大意就是在一棵树上,从一个点出发。每走一条边需要花一定时间。每到一个点,如果之前这个点没有走过,就要花一定时间,而且获得一定分数。问在T" role="presentation">TT单位时间内,最多可以得到多少分数(初始点有你自己确定)。


先思考思考~

具体的解法

这题看上去就是一道树形DP对吧,一副暴力不好打的样子。

首先,让我们假设一下初始点是固定的。

有个显然的结论:一个子树不可能重复进入两遍。

如果它重复进入了两遍,不如一次性走完,反正每个点的分数只能得到一次。

树形DP咋打?

设fi,j" role="presentation">fi,jfi,j表示在以i" role="presentation">ii为根的子树中,花了j" role="presentation">jj的时间,最终回到i" role="presentation">ii的最大分数。

设gi,j" role="presentation">gi,jgi,j表示在以i" role="presentation">ii为根的子树中,花了j" role="presentation">jj的时间,最终不一定回到i" role="presentation">ii的最大分数。

为什么这么设?树形DP嘛,将总的问题分解成每个子树中的问题,然后一一解决。

对于这题来说,一个子树对答案做出的贡献也就两种,要么走一圈之后出去再走其它的子树,要么走一圈之后永远待在子树中别出来了。

很明显,fi,j≤gi,j" role="presentation">fi,j≤gi,jfi,j≤gi,j

初值fi,ti=gi,ti=wi(fi,others=−∞)" role="presentation">fi,ti=gi,ti=wi(fi,others=−∞)fi,ti=gi,ti=wi(fi,others=−∞),不要问我为什么,太显然了……

在树形DP时,枚举子树,设这个子树的根节点为son" role="presentation">sonson。

先考虑f" role="presentation">ff的转移。

显然情况只有一种:

从i" role="presentation">ii出发,走son" role="presentation">sonson之前的子树,回到i" role="presentation">ii,然后又去son" role="presentation">sonson子树中转一圈,最终转回到i" role="presentation">ii。

fi,j⇐fi,j−2∗len−k+fson,k" role="presentation">fi,j⇐fi,j−2∗len−k+fson,kfi,j⇐fi,j−2∗len−k+fson,k

再考虑g" role="presentation">gg的转移。

这个情况有两种,分别是:

1. 从i" role="presentation">ii出发,走son" role="presentation">sonson之前的子树,回到i" role="presentation">ii,然后又去son" role="presentation">sonson子树中,永远地留在son" role="presentation">sonson子树里。

gi,j⇐fi,j−len−k+gson,k" role="presentation">gi,j⇐fi,j−len−k+gson,kgi,j⇐fi,j−len−k+gson,k

2. 另一个情况相对难理解。从i" role="presentation">ii出发,走son" role="presentation">sonson和son" role="presentation">sonson之前的子树,其中在son" role="presentation">sonson子树中转了一圈后回来。最终,在i" role="presentation">ii点或son" role="presentation">sonson之前的子树中停下来。也就是说,son" role="presentation">sonson子树只是作为一个中转站,转过一圈之后走出来,然后到别处停下来。

gi,j⇐gi,j−len∗2−k+fson,k" role="presentation">gi,j⇐gi,j−len∗2−k+fson,kgi,j⇐gi,j−len∗2−k+fson,k

知道了f" role="presentation">ff和g" role="presentation">gg,那就可以算出从根开始的最优解,即maxgroot,j" role="presentation">maxgroot,jmaxgroot,j。

所以我们可以枚举根,然后对于每个根做一遍树形DP。

时间复杂度O(n2T2)" role="presentation">O(n2T2)O(n2T2),显然过不了。

咋办?

我们之前求的是从根出发,那么不妨思考一下,在这个子树中,不一定会从根节点出发的情况。

设hi,j" role="presentation">hi,jhi,j表示在以i" role="presentation">ii为根的子树中,花了j" role="presentation">jj的时间,从i" role="presentation">ii子树中的某个点出发,经过i" role="presentation">ii点,最终在i" role="presentation">ii子树中停下来。

显然初值hi,ti=wi" role="presentation">hi,ti=wihi,ti=wi(其实初值这种东西真心不想打进博客里,只是可能有些童鞋需要……)

思考一下,情况有三种(比起前面的相对难理解,注意仔细思考):

1. 将i" role="presentation">ii以及son" role="presentation">sonson之前的子树求出的g" role="presentation">gg值,son" role="presentation">sonson里面的子树求出的g" role="presentation">gg值,合并在一起

怎么理解呢?形象地理解一下,f" role="presentation">ff、g" role="presentation">gg、h" role="presentation">hh值就像是条绳子所作出的贡献。其中f" role="presentation">ff至少两端在根节点上,g" role="presentation">gg至少一端在根节点上,h" role="presentation">hh的端点可以不再根节点上。而这样合并就是将两条g" role="presentation">gg绳各自的一头接在了一起,形成一个新的h" role="presentation">hh绳。

hi,j⇐gi,j−len−k+gson,k" role="presentation">hi,j⇐gi,j−len−k+gson,khi,j⇐gi,j−len−k+gson,k

2. 将i" role="presentation">ii的h" role="presentation">hh绳中,插入新的一段son" role="presentation">sonson的f" role="presentation">ff绳。这样就可以保证插入之后绳子没有中断。

hi,j⇐hi,j−2∗len−k+fson,k" role="presentation">hi,j⇐hi,j−2∗len−k+fson,khi,j⇐hi,j−2∗len−k+fson,k

3. 和上面类似,在son" role="presentation">sonson的h" role="presentation">hh绳中,插入新的一段i" role="presentation">ii以及son" role="presentation">sonson之前子树的f" role="presentation">ff绳。

hi,j⇐fi,j−2∗len−k+hson,k" role="presentation">hi,j⇐fi,j−2∗len−k+hson,khi,j⇐fi,j−2∗len−k+hson,k

这样求出所有的h" role="presentation">hh值,这样,答案即maxhi,j" role="presentation">maxhi,jmaxhi,j

对了,需要格外注意一下转移的顺序,按照h" role="presentation">hh、g" role="presentation">gg、f" role="presentation">ff的顺序(不然可能会出现叠加),还有一个六年级的同学都应该知道的j" role="presentation">jj要倒着枚举。

时间复杂度O(nT2)" role="presentation">O(nT2)O(nT2),可以过。

代码(模板?)

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 300
#define MAXTIME 300
inline void MAX(long long &a,long long b) {a<b?a=b:0;}//尝试更新
int n,time;
int w[MAXN+1],t[MAXN+1];
struct EDGE
{
int to,len;
EDGE *las;
} e[MAXN*2+1];
int ne;
EDGE *last[MAXN+1];
void link(int u,int v,int len){e[++ne]={v,len,last[u]};last[u]=e+ne;}
bool vis[MAXN+1];
long long f[MAXN+1][MAXTIME+1],g[MAXN+1][MAXTIME+1],h[MAXN+1][MAXTIME+1];
void dfs(int);
long long ans;
int main()
{
freopen("toyuq.in","r",stdin);
freopen("toyuq.out","w",stdout);
scanf("%d%d",&n,&time);
for (int i=1;i<=n;++i)
scanf("%d",&w[i]);
for (int i=1;i<=n;++i)
scanf("%d",&t[i]);
for (int i=1;i<n;++i)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
link(a,b,c),link(b,a,c);
}
dfs(1);
for (int i=1;i<=n;++i)
for (int j=t[i];j<=time;++j)
ans=max(ans,h[i][j]);
printf("%lld\n",ans);
return 0;
}
void dfs(int x)
{
vis[x]=1;
memset(f[x],254,sizeof f[x]);
memset(g[x],254,sizeof g[x]);
memset(h[x],254,sizeof h[x]);
if (t[x]>time)//这是一个剪枝,很显然,如果来x点所花费的时间大于time,那么就不用玩了
{
for (EDGE *ei=last[x];ei;ei=ei->las)
if (!vis[ei->to])
dfs(ei->to);
}
else
{
f[x][t[x]]=g[x][t[x]]=h[x][t[x]]=w[x];
for (EDGE *ei=last[x];ei;ei=ei->las)
if (!vis[ei->to])
{
dfs(ei->to);
for (int i=time;i>=t[x];--i)
{
//循环方式有些奇怪,其实就是将两种转移分开,少打一些if语句
int j=t[ei->to];
for (;i-ei->len-j-ei->len>=t[x];++j)
{
//下面可能会打得有些丑陋吧。
//为了方便理解,我将2*ei->len拆开成两个,分别表示进出.
//这个顺序是必须要注意,不然可能会出现叠加的情况。当然,如果你多开一个维,或者是打滚动,我也懒得理你。
//把一个状态的转移全部分开来打?显然不行啊,不然也可能会出现叠加。只能分开一些转移没有可能是来自当前的状态的式子。
MAX(h[x][i],max(g[x][i-ei->len-j]+g[ei->to][j],max(h[x][i-ei->len-j-ei->len]+f[ei->to][j],f[x][i-ei->len-j-ei->len]+h[ei->to][j])));
MAX(g[x][i],max(f[x][i-ei->len-j]+g[ei->to][j],g[x][i-ei->len-j-ei->len]+f[ei->to][j]));
MAX(f[x][i],f[x][i-ei->len-j-ei->len]+f[ei->to][j]);
}
for (;i-ei->len-j>=t[x];++j)
{
MAX(h[x][i],g[x][i-ei->len-j]+g[ei->to][j]);
MAX(g[x][i],f[x][i-ei->len-j]+g[ei->to][j]);
}
}
}
}
}

总结

可能会有些难以理解把,不过好好地思考思考还是可以理解的。这个树形DP比较奇妙。

以后见到这种在树上走的DP问题,就可以用类似这样的方法,或者是扩展。

莫名其妙的,我觉得这个DP方法很有意义。

基于在树上走的DP问题的更多相关文章

  1. 4712: 洪水 基于链分治的动态DP

    国际惯例的题面:看起来很神的样子......如果我说这是动态DP的板子题你敢信?基于链分治的动态DP?说人话,就是树链剖分线段树维护DP.既然是DP,那就先得有转移方程.我们令f[i]表示让i子树中的 ...

  2. 推荐系统之基于图的推荐:基于随机游走的PersonalRank算法

    转自http://blog.csdn.net/sinat_33741547/article/details/53002524 一 基本概念 基于图的模型是推荐系统中相当重要的一种方法,以下内容的基本思 ...

  3. 洛谷P1273 有线电视网 树上分组背包DP

    P1273 有线电视网 )逼着自己写DP 题意:在一棵树上选出最多的叶子节点,使得叶子节点的值 减去 各个叶子节点到根节点的消耗 >= 0: 思路: 树上分组背包DP,设dp[u][k] 表示 ...

  4. 图推荐-基于随机游走的personrank算法

    转自http://blog.csdn.net/sinat_33741547/article/details/53002524 一 基本概念 基于图的模型是推荐系统中相当重要的一种方法,以下内容的基本思 ...

  5. Java基于OpenCV实现走迷宫(图片+路线展示)

    Java基于OpenCV实现走迷宫(图片+路线展示) 由于疫情,待在家中,太过无聊.同学发了我张迷宫图片,让我走迷宫来缓解暴躁,于是乎就码了一个程序出来.特此记录. 原图: 这张图,由于不是非常清晰, ...

  6. 洛谷 P3177 [HAOI2015]树上染色 树形DP

    洛谷 P3177 [HAOI2015]树上染色 树形DP 题目描述 有一棵点数为 \(n\) 的树,树边有边权.给你一个在 \(0 \sim n\)之内的正整数 \(k\) ,你要在这棵树中选择 \( ...

  7. bzoj 4033: [HAOI2015]树上染色 [树形DP]

    4033: [HAOI2015]树上染色 我写的可是\(O(n^2)\)的树形背包! 注意j倒着枚举,而k要正着枚举,因为k可能从0开始,会使用自己更新一次 #include <iostream ...

  8. HDU - 59562016ACM/ICPC亚洲区沈阳站I - The Elder 树上斜率优化dp

    题意:给定上一棵树,然后每条边有一个权值,然后每个点到 1 的距离有两种,第一种是直接回到1,花费是 dist(1, i)^2,还有另一种是先到另一个点 j,然后两从 j 向1走,当然 j 也可以再向 ...

  9. 51nod 1486 大大走格子——dp

    有一个h行w列的棋盘,里面有一些格子是不能走的,现在要求从左上角走到右下角的方案数. Input 单组测试数据. 第一行有三个整数h, w, n(1 ≤ h, w ≤ 10^5, 1 ≤ n ≤ 20 ...

随机推荐

  1. Mysql ---部署,创建用户

    版本:mysql-5.7.18-win32 步骤: 1 准备my.ini文件放在bin同级目录 My.ini文件可以设置bsedir/datadir/port等等 2 初始化数据库(5.7版本需要初始 ...

  2. [JZOJ3348] 【NOI2013模拟】秘密任务

    题目 题目大意 给你一个无向图,你要割掉一些边使得\(1\)到\(n\)的所有最短路径被阻截. 割掉一个边\((u,v)\)的代价为\(a_u\)或\(a_v\)(记为两种不同的方案). 问最小代价及 ...

  3. OSS支持IPV6/IPV4双栈访问域名

    摘要: OSS开放IPv6/IPv4双栈域名,可同时支持IPv6/IPv4客户端的访问,支持下一代互联网技术IPv6,可服务海量物理网设备连接等应用场景. 下一代IP协议 IPv4地址已接近枯竭,被誉 ...

  4. <Django>第一篇:入门的例子

    1.MVT框架 Model(模型):数据库交互相关.在这部分一般需要进行三个操作: (1)面向数据库:模型对象.列表 (2)定义模型类:指定属性及类型,确定表结构(设计表),需要迁移(生成表) (3) ...

  5. HYNB Round 15: PKU Campus 2019

    HYNB Round 15: PKU Campus 2019 C. Parade 题意 将平面上n*2个点安排在长度为n的两行上. 做法 首先可以忽略每个点之间的影响,只用考虑匹配即可 然后把所以点归 ...

  6. [WPF自定义控件]?使用WindowChrome自定义Window Style

    原文:[WPF自定义控件]?使用WindowChrome自定义Window Style 1. 为什么要自定义Window 对稍微有点规模的桌面软件来说自定义的Window几乎是标配了,一来设计师总是克 ...

  7. mysql的建表约束

    主键约束(primary key) 主键约束能够唯一确定一张表中的记录,也就是可以通过某个字段添加约束,就可以是的该字段不重复,且不为空 create table user (id int prima ...

  8. 18-4-bind

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. 16-3-es5解析顺序

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. vue 简单留言本

    代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8 ...