[倍增] 将带权树分割为数量最少的链 CF1059E

给定一棵根节点为1的有根树,每个节点包含一个权重w_i,目标是将其分割为最少的垂直路径,每条路径不超过L个节点,且每个路径上权重之和不超过S。输出能达成条件的最小路径数,若无法分割则输出-1。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

E. Split the Tree

time limit per test

2 seconds

memory limit per test

256 megabytes

input

standard input

output

standard output

You are given a rooted tree on nn vertices, its root is the vertex number 11. The ii-th vertex contains a number wiwi. Split it into the minimum possible number of vertical paths in such a way that each path contains no more than LL vertices and the sum of integers wiwi on each path does not exceed SS. Each vertex should belong to exactly one path.

A vertical path is a sequence of vertices v1,v2,…,vkv1,v2,…,vk where vivi (i≥2i≥2) is the parent of vi−1vi−1.

Input

The first line contains three integers nn, LL, SS (1≤n≤1051≤n≤105, 1≤L≤1051≤L≤105, 1≤S≤10181≤S≤1018) — the number of vertices, the maximum number of vertices in one path and the maximum sum in one path.

The second line contains nn integers w1,w2,…,wnw1,w2,…,wn (1≤wi≤1091≤wi≤109) — the numbers in the vertices of the tree.

The third line contains n−1n−1 integers p2,…,pnp2,…,pn (1≤pi<i1≤pi<i), where pipi is the parent of the ii-th vertex in the tree.

Output

Output one number  — the minimum number of vertical paths. If it is impossible to split the tree, output −1−1.

Examples

input

Copy

3 1 3
1 2 3
1 1

output

Copy

3

input

Copy

3 3 6
1 2 3
1 1

output

Copy

2

input

Copy

1 1 10000
10001

output

Copy

-1

Note

In the first sample the tree is split into {1}, {2}, {3}{1}, {2}, {3}.

In the second sample the tree is split into {1, 2}, {3}{1, 2}, {3} or {1, 3}, {2}{1, 3}, {2}.

In the third sample it is impossible to split the tree.

 

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int mn = 1e5 + 10;

int f[mn][30], dep[mn], up[mn], top[mn];
ll w[mn], sum[mn][30];

int main()
{
	int n, L; ll S;	scanf("%d %d %lld", &n, &L, &S);
	for (int i = 1; i <= n; i++)
	{
		scanf("%lld", &w[i]);
		if (w[i] > S)
		{
			printf("-1\n");
			return 0;
		}
	}
	for (int i = 2; i <= n; i++)	scanf("%d", &f[i][0]);

	// 父节点编号小于子节点
	dep[1] = 1;
	for (int i = 1; i <= n; i++)	// 相邻层之间
	{
		sum[i][0] = w[f[i][0]];		// 这个点之上节点的sum
		dep[i] = dep[f[i][0]] + 1;
	}
	for (int j = 1; j <= 17; j++)	/// 倍增
	{
		for (int i = 1; i <= n; i++)
		{
			int tf = f[i][j - 1];
			f[i][j] = f[tf][j - 1];
			sum[i][j] = sum[i][j - 1] + sum[tf][j - 1];
		}
	}

    for (int i = 1; i <= n; i++)
	{
		int id = i, cnt = 1; ll tsum = w[i];
		for (int j = 17; j >= 0; j--)
		{	// 限制条件下点i能扩展到的最浅的点
			if (f[id][j] != 0 && cnt + (1 << j) <= L && tsum + sum[id][j] <= S)
			{
				cnt += (1 << j), tsum += sum[id][j];
				id = f[id][j];	// id 上跃
			}
		}
		up[i] = id;
	}

	int ans = 0;
	for (int i = n; i >= 1; i--)
	{
		if (!top[i])	// 新开一条链
			ans++, top[i] = up[i];
		int fa = f[i][0];
		if (dep[top[i]] <= dep[fa] && (!top[fa] || dep[top[i]] < dep[top[fa]]))
			top[fa] = top[i];	// 子节点创造的链覆盖的父节点们赋值了与子节点相同的可达最浅点
	}
	printf("%d\n", ans);

	return 0;
}

 

 

### LCA 最近公共祖先问题在树结构中使用倍增法的解题方法 #### 倍增算法简介 倍增算法是一种高效的技术,在路径查询、区间问题以及树的祖先查询等方面有着广泛应用。这种算法特别适合用于解决涉及多个层次关系的问题,比如最近公共祖先(LCA)问题[^1]。 #### LCA 定义及其重要性 在有根树中,对于两个节点 \(u\) 和 \(v\),LCA 是所有公共祖先中最深的那个节点。这个概念不仅限于普通的父子关系,还包括节点本身作为自己祖先的情况。因此,在任何给定的一对节点间都存在唯一的LCA[^2]。 #### 使用倍增法求解LCA的具体过程 为了提高查找效率,通常会对树进行预处理。具体来说,就是利用二进制表示来记录每个节点向上跳转\(2^k\)步后的父节点位置。这样做的好处是可以快速定位到目标节点之间的共同祖先,从而大大减少了实际计算量。以下是基于此思想的一种常见实现方式: ```cpp const int MAXN = 1e5 + 7; int fa[MAXN][20]; // 存储第i个结点往上走2^j步到达的父亲编号 int dep[MAXN]; // 结点深度数组 void dfs(int u,int father){ fa[u][0]=father; // 初始化fa[i][0],即直接父亲 dep[u]=dep[father]+1; for (auto &v : adj[u]) { if(v==father) continue; dfs(v,u); } } // 预处理f表 for(int j=1;(1<<j)<n;j++) for(int i=1;i<=n;i++) if(fa[i][j-1]!=-1) fa[i][j]=fa[fa[i][j-1]][j-1]; pair<int,int> getlca(int a,int b){ while(a!=b){ if(dep[a]<dep[b]) swap(a,b); int k=log2(dep[a]-dep[b]); a=fa[a][k]; } return {a,dep[a]}; } ``` 上述代码片段展示了如何构建倍增表格`fa[][]`并通过深度优先搜索初始化各个节点的信息。之后通过简单的循环操作即可完成任意两点之间LCA的查询工作[^4]。 #### 应用场景扩展 除了基本的LCA查询外,倍增法还可以与其他高级数据结构相结合,如并查集和树剖分等,进一步优化复杂度较高的题目解答方案。此外,在动态规划领域也经常能看到它的身影,尤其是在那些涉及到子树范围内属性汇总或者状态转移方向确定等问题上[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值