<更新提示>

<第一次更新>


<正文>

kamp

Description

jz 市的云台山是个很美丽的景区,小 x 暑期到云台山打工,他的任务是开景区的大巴。

云台山景区有 N 个景点,这 N 个景点由 N-1 条道路连接而成,我们保证这 N 个景点之 间有且仅有一条路径相连,并且每条道路开车经过的时间不一定相同。

现在小 x 会提前知道有 K 个人要坐车,并且每个人都有一个景点作为目标景点,小 x 必须要把每个人送到他们要去的景点。

不过,小 x 可以指定这 K 个人去某个景点集合,这样他就从这个集合点出发,分别去 送这 K 个的人。

现在小 x 想知道,选哪个点集合最省时间,当然,为了更综合的判断,他想知道,以 每个点作为集合点,花费的最小时间是多少?

Input Format

第一行两个整数 N 和 K。

接下来 N-1 行,每行 3 个整数,Xi,Yi 和 Vi,表示从第 Xi 个景点到第 Yi 个景点有一 条道路相连,这条道路消耗的时间是 Vi。 Xi 和 Yi 在[1..N]范围内,Vi 在[1..100000]之间。

接下来 K 行,每行一个整数 Zi,表示第 i 个人要去的景点。

Output Format

N 行,第 i 行输出为 Zi,表示以第 i 个景点作为集合地点,把 K 队人送到各自要去的景 点花费的最小总时间,并且小 x 送完最后一个人后,他的任务就完成了。

Sample Input

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

Sample Output

5
3
7
2
2

解析

很容易想到是树形\(dp\),并且要换根。

可以先设计出普通的\(dp\),设\(f_{x}\)代表以\(x\)为根的子树中送完所有人并回到\(x\)的路程和。为什么要这样设计,因为这样方便转移,我们再设一个\(g_x\)代表以\(x\)为根的子树中送完所有人不回到\(x\)的路程和,就可以列出状态转移方程了。

\[f_x=\sum_{y\in son(x)}f_y+2\times e(x,y)\\g_x=f_x-\max_{y\in son(x)}\{f_y+e(x,y)-g_y\}
\]

那么根据题意,答案就是\(g_x\),应该比较好理解,那么我们就得到了一个\(O(n^2)\)的做法。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N = 2020;
struct edge { int ver,val,next; } e[N*2];
int n,k,t,Head[N],sum[N],tar[N],f[N],g[N];
inline void insert(int x,int y,int v) { e[++t] = (edge){y,v,Head[x]} , Head[x] = t; }
inline void input(void)
{
scanf("%d%d",&n,&k);
for (int i=1;i<n;i++)
{
int x,y,v;
scanf("%d%d%d",&x,&y,&v);
insert( x , y , v );
insert( y , x , v );
}
for (int i=1;i<=k;i++)
{
int x; scanf("%d",&x);
tar[x]++;
}
}
inline void dfs(int x,int fa)
{
int Max = 0; sum[x] = tar[x];
for (int i=Head[x];i;i=e[i].next)
{
int y = e[i].ver;
if ( y == fa ) continue;
dfs( y , x );
sum[x] += sum[y];
if ( !sum[y] ) continue;
f[x] += f[y] + 2 * e[i].val;
Max = max( Max , f[y] + e[i].val - g[y] );
}
g[x] = f[x] - Max;
}
int main(void)
{
freopen("kamp.in","r",stdin);
freopen("kamp.out","w",stdout);
input();
for (int i=1;i<=n;i++)
{
memset( f , 0 , sizeof f );
memset( g , 0 , sizeof g );
memset( sum , 0 , sizeof sum );
dfs( i , 0 );
printf("%d\n",g[i]);
}
return 0;
}

然后直接考虑换根即可。状态\(g\)的形式是很复杂的,我们不妨进行化简,令:

\[g_x=f_x-g'_x
\]

那么就有$$g_x=f_x-\max_{y\in son(x)}{f_y+e(x,y)-g_y}\=f_x-\max_{y\in son(x)}{f_y+e(x,y)-f_y+g'y}\=f_x-\max{y\in son(x)}{e(x,y)+g'_y}$$

又有$$g_x=\max_{y\in son(x)}{e(x,y)+g'_y}$$

\(ok\),事实上\(g'_x\)的意义也就是以\(x\)出发,走向\(x\)子树中最远关键点的距离,最后要的答案\(g_x=f_x+g'_x\)。

我们发现\(g'_x\)很容易换根求,只需要考虑向下走的最长链和向上走的最长链,记录一下最大值和次大值来更新即可。

那么问题就转换为了求\(f_x\)。其实考虑一下路径的形态很容易得到换根方程:

\[f_y=\begin{cases}f_x& \exists\ k\in subtree(y)\\f_x-2\times e(x,y)& \forall \ k\in subtree(y)\\f_x+2\times e(x,y) & \forall \ k\not\in subtree(y) \end{cases}
\]

其中\(k\)代表关键点,于是就可以\(O(n)\)求了。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N = 500020;
struct edge { int ver,val,next; } e[N*2];
int n,k,t,Head[N],sum[N],tar[N],p[N],root,deg[N];
long long f[N],d[N][2],g[N],_f[N];
inline void insert(int x,int y,int v) { e[++t] = (edge){y,v,Head[x]} , Head[x] = t; }
inline void input(void)
{
scanf("%d%d",&n,&k);
for (int i=1;i<n;i++)
{
int x,y,v;
scanf("%d%d%d",&x,&y,&v);
insert( x , y , v );
insert( y , x , v );
deg[x]++ , deg[y]++;
}
for (int i=1;i<=k;i++)
{
int x; scanf("%d",&x);
tar[x]++;
}
}
inline void dfs(int x,int fa)
{
sum[x] = tar[x];
for (int i=Head[x];i;i=e[i].next)
{
int y = e[i].ver;
if ( y == fa ) continue;
dfs( y , x );
sum[x] += sum[y];
if ( !sum[y] ) continue;
f[x] += f[y] + 2LL * e[i].val;
long long val = d[y][1] + e[i].val;
if ( val >= d[x][1] )
d[x][0] = d[x][1] , d[x][1] = val , p[x] = y;
else if ( val > d[x][0] ) d[x][0] = val;
}
}
inline void dp(int x,int fa)
{
for (int i=Head[x];i;i=e[i].next)
{
int y = e[i].ver;
if ( y == fa ) continue;
_f[y] = _f[x] - ( k - sum[y] == 0 ) * 2LL * e[i].val;
_f[y] += 2LL * e[i].val * ( sum[y] == 0 );
if ( p[x] != y )
g[y] = max( g[y] , d[x][1] + e[i].val );
else g[y] = max( g[y] , d[x][0] + e[i].val );
g[y] = max( g[y] , g[x] + e[i].val );
dp( y , x );
}
}
inline void findroot(void)
{
for (int i=1;i<=n;i++)
if ( tar[i] ) root = i;
}
int main(void)
{
freopen("kamp.in","r",stdin);
freopen("kamp.out","w",stdout);
input();
findroot();
dfs( root , 0 );
_f[root] = f[root];
dp( root , 0 );
for (int i=1;i<=n;i++)
printf("%lld\n",_f[i]-max(g[i],d[i][1]));
return 0;
}

<后记>

『kamp 树形dp』的更多相关文章

  1. 『大 树形dp』

    大 Description 滑稽树上滑稽果,滑稽树下你和我,滑稽树前做游戏,滑稽多又多.树上有 n 个节点,它们构成了一棵树,每个节点都有一个滑稽值. 一个大的连通块是指其中最大滑稽值和最小滑稽值之差 ...

  2. 『选课 树形dp 输出方案』

    这道题的树上分组背包的做法已经在『选课 有树形依赖的背包问题』中讲过了,本篇博客中主要讲解将多叉树转二叉树的做法,以便输出方案. 选课 Description 学校实行学分制.每门的必修课都有固定的学 ...

  3. 『战略游戏 最大利润 树形DP』

    通过两道简单的例题,我们来重新认识树形DP. 战略游戏(luoguP1026) Description Bob喜欢玩电脑游戏,特别是战略游戏.但是他经常无法找到快速玩过游戏的办法.现在他有个问题.他要 ...

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

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

  5. 『You Are Given a Tree 整体分治 树形dp』

    You Are Given a Tree Description A tree is an undirected graph with exactly one simple path between ...

  6. 『快乐链覆盖 树形dp』

    快乐链覆盖 Description 给定一棵 n 个点的树,你需要找至多 k 条互不相交的路径,使得它们的长度之和最大 定义两条路径是相交的:当且仅当存在至少一个点,使得这个点在两条路径中都出现 定义 ...

  7. 『树上匹配 树形dp』

    树上匹配 Description 懒惰的温温今天上班也在偷懒.盯着窗外发呆的温温发现,透过窗户正巧能看到一棵 n 个节点的树.一棵 n 个节点的树包含 n-1 条边,且 n 个节点是联通的.树上两点之 ...

  8. 【BZOJ3743】[Coci2015]Kamp 树形DP

    [BZOJ3743][Coci2015]Kamp Description 一颗树n个点,n-1条边,经过每条边都要花费一定的时间,任意两个点都是联通的. 有K个人(分布在K个不同的点)要集中到一个点举 ...

  9. bzoj 3743 [ Coci 2015 ] Kamp —— 树形DP

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3743 一开始想到了树形DP,处理一下子树中的最小值,向上的最小值,以及子树中的最长路和向上的 ...

随机推荐

  1. vue如何导入外部js文件(es6)

    也许大家都是使用习惯了es5的写法喜欢直接用<Script>标签倒入js文件,但是很是可惜,这写法.在es6,或则说vue环境下不支持 真的写法是怎样? 首先.我们要改造我们要映入的外部j ...

  2. 微信小程序的线程架构

    小程序的线程架构 每个小程序包含一个描述整体程序的app实例和多个描述页面的page. 其中app由3个文件构成: app.json 公共配置文件 app.wxss 公共样式文件 app.js 主体逻 ...

  3. Rust中的Cargo工作空间实践

    这是为了开发大型程序,分治crate用的. 目录结构如下: 一,根cargo.toml内容 [workspace] members = [ "adder", "add-o ...

  4. Nginx ServerName 配置说明

    Nginx强大的正则表达式支持,可以使server_name的配置变得很灵活,如果你要做多用户博客,那么每个用户拥有自己的二级域名也就很容易实现了.下面我就来说说server_name的使用吧:ser ...

  5. NLP之CRF应用篇(序列标注任务)

    1.CRF++的详细解析 完成的是学习和解码的过程:训练即为学习的过程,预测即为解码的过程. 模板的解析: 具体参考hanlp提供的: http://www.hankcs.com/nlp/the-cr ...

  6. 前端Vue项目——首页/课程页面开发及Axios请求

    一.首页轮播图 1.elementUI走马灯 elementUI中 Carousel 走马灯,可以在有限空间内,循环播放同一类型的图片.文字等内容. 这里使用指示器样式,可以将指示器的显示位置设置在容 ...

  7. koa2+mysql+vue实现用户注册、登录、token验证

    说明: node.js提供接口,vue展现页面,前后端分离,出于编辑器功能和编辑习惯,vue用HbuilderX,node.js用VScode.(PS:仅作为学习笔记,如有不当之处欢迎指出,在此先谢为 ...

  8. [LeetCode] 457. Circular Array Loop 环形数组循环

    You are given a circular array nums of positive and negative integers. If a number k at an index is ...

  9. Spring Cloud Gateway重试机制

    前言 重试,我相信大家并不陌生.在我们调用Http接口的时候,总会因为某种原因调用失败,这个时候我们可以通过重试的方式,来重新请求接口. 生活中这样的事例很多,比如打电话,对方正在通话中啊,信号不好啊 ...

  10. 热情组——项目冲刺 Day1

    项目相关 作业相关 具体描述 班级 班级链接 作业要求 链接地址 团队名称 热情组 作业目标 实现软件的生成,以及在福大的传播 Github链接 链接地址 SCRUM部分: 成员昵称 昨日目标 昨日进 ...