[CF1059E Round#514 Div.2]Split the Tree——[贪心+树上倍增]

【原题】
在这里插入图片描述
【题目翻译】

现有n个点组成一棵以1为根的有根树,第i个点的点权为wi,需将其分成若干条垂直路径使得每一个点当且仅当被一条垂直路径覆盖,同时,每条垂直路径长度不能超过L,点权和不能超过S,求最少需要几条垂直路径才能满足要求。特别地,无解输出-1。

一条垂直路径是一条包含v1,v2…vk的路径,使得vi(i>=2)是vi-1的父亲。

【输入格式】

第一行三个整数n,L,S,表示节点个数,链的最长长度,链的最大权值和

第二行n个整数wi表示第i个节点的权值

第三行n-1个整数fi表示第i个节点的父亲

【输出格式】

一行一个整数,表示最小分割成的链数,无解输出-1

S a m p l e    I n p u t Sample~~Input Sample  Input

3 1 3
1 2 3
1 1

S a m p l e    O u t p u t Sample~~Output Sample  Output

3

【题意分析】

一开始以为是个树形DP,(其实树形DP也可以做)

只有单点权值大于S才是无解,因为其他情况都可以全部切成单点。

我们记top[x]表示从x开始向上延伸最多能延伸到哪里。

这个怎么维护?

可以用树上倍增处理出father[x][i]表示x向上跳 2 i 2^i 2i个节点的祖先是谁,
prefix[x]表示从根节点到x的路径前缀和

i i i从20往下枚举,倍增地跳祖先。
如果 f a t h e r [ n o w ] [ i ] father[now][i] father[now][i]存在,而且 f a t h e r [ n o w ] [ i ] father[now][i] father[now][i] n o w now now的路径和与节点数不超过限制,那么就可以愉快地更新啦~

路径和可以利用 p r e f i x prefix prefix数组 O ( 1 ) O(1) O(1)求出,路径长可以用 d e p t h depth depth数组 O ( 1 ) O(1) O(1)求出

代码里有详细注释

Code:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define int long long
#define MAXN 200000
using namespace std;

struct fls {
	int to, next;
}edge[MAXN << 1];

int head[MAXN << 1], depth[MAXN], top[MAXN], father[MAXN][22];
int reach[MAXN], prefix[MAXN], a[MAXN], cnt, ans, n, val, len;

inline int read () {
	register int s = 0, w = 1;
	register char ch = getchar ();
	while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
	while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
	return s * w; 
}

inline void connect (int u, int v) {
	edge[++cnt].to = v, edge[cnt].next = head[u], head[u] = cnt;
}

void DFS1 (int now, int fa, int d) {
	//预处理各个数据,top[now]一开始默认就是now
	prefix[now] = prefix[fa] + a[now], depth[now] = d, top[now] = now;
	//倍增,father[i][j+1]=father[father[i][j]][j];
	for (register int i = 0; father[now][i]; i++)
		father[now][i + 1] = father[father[now][i]][i];
	//自己也算一个,所以要减一
	int rest = len - 1;
	for (register int i = 20; i >= 0; i--) {
		//o是当前链顶端,u是跳倍增
		int o = top[now], u = father[o][i];
		//如果满足条件就更新
		if (u && prefix[now] - prefix[u] + a[u] <= val && (1 << i) <= rest)
			top[now] = u, rest -= (1 << i);
	}
	for (register int i = head[now]; i; i = edge[i].next) {
		int v = edge[i].to; DFS1 (v, now, d + 1);
	}
}

void DFS2 (int now) {
	int Lowest = 0;
	for (register int i = head[now]; i; i = edge[i].next) {
		int v = edge[i].to; DFS2 (v);
		//儿子能达到的不能是儿子自己,不然就不能跟我形成链
		//贪心,肯定是选延伸到深度最浅(最往上面)的儿子
		if (reach[v] != v && (! Lowest || depth[Lowest] > depth[reach[v]]))
			Lowest = reach[v];
	}
	//如果不行,新开一条链,ans++
	if (! Lowest) {
		ans++; reach[now] = top[now];
	}else reach[now] = Lowest;
}

signed main () {
	n = read (), len = read (), val = read ();
	for (register int i = 1; i <= n; i++) {
		a[i] = read ();
		//特判单点超过限制
		if (a[i] > val) {
			puts ("-1"); return 0;
		}
	}
	for (register int i = 2; i <= n; i++) {
		int x = read ();
		//i向上跳1个,也就是father[i][0]=x
		father[i][0] = x, connect (x, i);
	}
	DFS1 (1, 0, 1), DFS2 (1);
	printf ("%lld\n", ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值