首页 > 其他分享 >【luogu P6419】Kamp(换根DP)

【luogu P6419】Kamp(换根DP)

时间:2022-09-27 15:44:41浏览次数:80  
标签:子树 int luogu Kamp P6419 DP

Kamp

题目链接:luogu P6419

题目大意

一棵树上有一些点有人,边有通过的长度,然后对于每个点,你从这个点出发经过所有人(不用回到原来位置)的最短时间。
其它人不会动,只有你去找人。

思路

首先只考虑一个点,发现如果回到原来位置是比较好搞的,就每次走完子树的里面要的就上来,如果子树里面没有要走的就不走。
(大概是 \(f_x=\sum\limits f_y+2e_{x,y}\),因为要走过去走回来,注意 \(y\) 要保证子树里面有人)

至于多个点换根 DP 即可。

然后考虑不走回来,那你少了的长度就是从最后一个人走回自己的距离。
那贪心的选离你最远的,思考一下发现是可以的因为可以最晚走到的条件是子树内除了自己没有人,安排一下子树的遍历顺序即可。

那这个对于每个点求距离它最远的人其实不难,就分成子树内和字数外,子树内好统计,子树外就维护子树内次小(不能跟最小在同一个儿子里面),然后要么从上面的 \(up\) 下来,要么在你这里开始往下(因为不能自己上自己下所以要维护次小)

然后就可以啦!

代码

#include<cstdio>
#include<iostream>
#define ll long long

using namespace std;

const int N = 5e5 + 100;
int n, k, a[N], le[N], KK;
int dis[N][2], sz[N], up[N];
ll ans[N], f[N];
bool p[N];
struct node {
	int x, to, nxt;
}e[N << 1];

void add(int x, int y, int z) {e[++KK] = (node){z, y, le[x]}; le[x] = KK;}

ll DP(int now, int father) {
	ll re = 0; if (p[now]) sz[now] = 1;
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			ll x = DP(e[i].to, now); sz[now] += sz[e[i].to];
			if (!x && !p[e[i].to]) continue;
			x += 2ll * e[i].x; re += x;
			if (!x) continue;
			//不能是同一个子树所以不用再比次大
			if (dis[now][0] < dis[e[i].to][0] + e[i].x) dis[now][1] = dis[now][0], dis[now][0] = dis[e[i].to][0] + e[i].x;
				else if (dis[now][1] < dis[e[i].to][0] + e[i].x) dis[now][1] = dis[e[i].to][0] + e[i].x;
		}
	return f[now] = re;
}

void work(int now, int father) {
	ans[now] = f[now];
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			if (sz[1] - sz[e[i].to]) {
				up[e[i].to] = up[now] + e[i].x;
				if (dis[now][0] == dis[e[i].to][0] + e[i].x) up[e[i].to] = max(up[e[i].to], dis[now][1] + e[i].x);
					else up[e[i].to] = max(up[e[i].to], dis[now][0] + e[i].x);
			}
			
			bool re = 0, ree = 0;
			if (f[e[i].to] || p[e[i].to]) {
				f[now] -= 2ll * e[i].x + f[e[i].to]; re = 1;
			}
			if (f[now] || p[now]) {
				f[e[i].to] += 2ll * e[i].x + f[now]; ree = 1;
			}
			work(e[i].to, now);
			if (ree) {
				f[e[i].to] -= 2ll * e[i].x + f[now];
			}
			if (re) {
				f[now] += 2ll * e[i].x + f[e[i].to];
			}
		}
}

int main() {
	scanf("%d %d", &n, &k);
	for (int i = 1; i < n; i++) {
		int x, y, z; scanf("%d %d %d", &x, &y, &z);
		add(x, y, z); add(y, x, z);
	}
	for (int i = 1; i <= k; i++) scanf("%d", &a[i]), p[a[i]] = 1;
	
	ans[1] = DP(1, 0);
	work(1, 0);
	for (int i = 1; i <= n; i++) printf("%lld\n", ans[i] - max(up[i], dis[i][0]));
	
	return 0;
}

标签:子树,int,luogu,Kamp,P6419,DP
From: https://www.cnblogs.com/Sakura-TJH/p/luogu_P6419.html

相关文章

  • [luogu3980]志愿者招募
    记$x_{i}$为第$i$类志愿者数量$,y_{j}=\sum_{j\in[s_{i},t_{i}]}x_{i}-a_{j}$​,则问题即$$\foralli\in[1,m],x_{i}\ge0\\\forallj\in[1,n],y_{j}\ge0\\y_{1}-\sum_{......
  • 【luogu CF1109E】Sasha and a Very Easy Test(线段树)
    SashaandaVeryEasyTest题目链接:luoguCF1109E题目大意维护一个长度为n的序列,有区间乘,单点除(保证能整除),区间求和答案对p取模。p不一定是质数。思路麻了考场......
  • [luogu p8251] [NOI Online 2022 提高组] 丹钓战
    [P8251NOIOnline2022提高组]丹钓战-洛谷|计算机科学教育新生态(luogu.com.cn)容易发现对于一次查询\([L,R]\),\(L\)一定是第一个入栈的,也是成功的,答案至少为......
  • luogu P1052 [NOIP2005 提高组] 过河
    [NOIP2005提高组]过河题目描述在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳......
  • luogu P2233 [HNOI2002]公交车路线
    [HNOI2002]公交车路线题目描述在长沙城新建的环城公路上一共有\(8\)个公交站,分别为A、B、C、D、E、F、G、H。公共汽车只能够在相邻的两个公交站之间运行,因此你从某一......
  • luogu P1772 [ZJOI2006] 物流运输 (dp, 最短路)
    https://www.luogu.com.cn/problem/P1772虽然是图论背景,但是1-n天之间是线性关系。没法贪心决策,考虑dp:我本来写的dp是i-1转移到i,但是这样没法处理哪一天能走哪些最短路......
  • luogu P1410 子序列
    子序列题目描述给定一个长度为\(N\)(\(N\)为偶数)的序列,问能否将其划分为两个长度为\(N/2\)的严格递增子序列。输入格式若干行,每行表示一组数据。对于每组数据,首......
  • 【luogu P4218】珠宝商(SAM)(点分治)(根号分治)
    珠宝商题目链接:luoguP4218题目大意给你一棵树,每个点有一个字符。再给你一个字符串s。然后问你树上的所有简单的路径在s上的出现次数的和。思路一个比较神奇的题......
  • luogu P7632 题解
    一.思路我们可以先把时间换成以秒为单位的,然后在枚举每一秒时谁领先。二.重要点我们可以用string读入时间,再用一个函数以秒为单位提取出来(在程序中的函数名:tiqu)......
  • luogu 4025
    [PA2014]Bohater题目描述在一款电脑游戏中,你需要打败\(n\)只怪物(从\(1\)到\(n\)编号)。为了打败第\(i\)只怪物,你需要消耗\(d_i\)点生命值,但怪物死后会掉落血药......