【CodeForces - 1059E】Split the Tree

本文探讨了一道关于树剖分的复杂算法问题,旨在将一棵带权有根树分解为最少数量的链,每条链的顶点数量及权重总和需满足特定条件。文章详细介绍了使用树剖分和倍增法进行优化的解决方案,包括关键结论的归纳证明,以及具体的代码实现。

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


@题目描述 - English@

time limit per test:2 seconds
memory limit per test:256 megabytes

You are given a rooted tree on n vertices, its root is the vertex number 1. The i-th vertex contains a number wi. Split it into the minimum possible number of vertical paths in such a way that each path contains no more than L vertices and the sum of integers wi on each path does not exceed S. Each vertex should belong to exactly one path.
A vertical path is a sequence of vertices v1,v2,…,vk where vi (i≥2) is the parent of vi−1.

Input
The first line contains three integers n, L, S (1≤n≤105,1≤L≤105, 1≤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 n integers w1,w2,…,wn (1≤wi≤109) — the numbers in the vertices of the tree.
The third line contains n−1 integers p2,…,pn (1≤pi<i), where pi is the parent of the i-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.

Examples
input
3 1 3
1 2 3
1 1
output
3

input
3 3 6
1 2 3
1 1
output
2

input
1 1 10000
10001
output
-1

Note
In the first sample the tree is split into {1}, {2}, {3}.
In the second sample the tree is split into {1, 2}, {3} or {1, 3}, {2}.
In the third sample it is impossible to split the tree.

@题目翻译@

将一棵n个点的带权有根树剖分成尽量少的链,使得(1)链的两个端点是祖先关系(2)链含有的顶点个数小于等于L(3)链上所有点的点权和小于等于S。
求出最少链的数量,如果无解输出-1。N<=105

@分析@

【当时比赛的时候我真的想到了正解来着……】
【只是自己带了一个“树上倍增永远写不对”的buff……所以没有A掉QAQ。】
【后来就去调D题。然后D题平方差公式推错……整场比赛就崩掉了QAQ……】

首先,如果所有的链都只包含一个结点,那么如果存在结点i使得它的点权wi>S则无解,否则就有解。

我们令 a[i] 表示将 i 这颗子树按题意剖分得到的最小链条数,再令 s u m [ i ] = ( ∑ j j 是 i 的 孩 子 a [ j ] ) sum[i] = (\sum_j^{j是i的孩子}a[j]) sum[i]=(jjia[j])。不难发现 a[i] 有上界: a [ i ] &lt; = s u m [ i ] + 1 a[i]&lt;=sum[i]+1 a[i]<=sum[i]+1。上式表示将 i 单独剖分成一条链的链数。再进一步思考,a[i] 也有下界: s u m [ i ] &lt; = a [ i ] sum[i]&lt;=a[i] sum[i]<=a[i]。上式表示增加一个结点过后,链的数量不会变少。

于是: a [ i ] = s u m [ i ] 或 s u m [ i ] + 1 a[i]=sum[i]或sum[i]+1 a[i]=sum[i]sum[i]+1
【为简化描述,如果 a [ i ] = s u m [ i ] a[i]=sum[i] a[i]=sum[i]我们就称 i 为黑点,否则为灰点。】

如果结点 i 是黑点,则它不能够单独成链,那么它肯定与它的某一个孩子共链。所以有这样一个结论:最优策略下,链底端的结点 d 一定满足灰点。

再一步推导,就能得到一个关键的结论:在以结点 i 为根的子树中,如果存在灰点 d ,使得 i~d 是一条合法的链,则 i 为黑点。这个结论可以用归纳法证明(当然也可以猜测它是对的,跳过证明)。

然后,根据上面那个结论可知,灰点一定是在链的底端。所以有链的数量 = 灰点数量。

那么,现在开始描述算法:从根节点开始dfs。将所有的儿子都遍历完后,如果当前点 rt 为灰点,则沿着 rt 往上走,直到走到 p 使得 p ~ rt这条链不合法。然后 p ~ rt上的(除p,rt以外)点记为黑点;否则如果 rt 为黑点,不作任何操作。

找结点 p 可以用倍增法。标记 p~rt 上的点可以用树链剖分。

@代码@

啊啊啊……总觉得还是很可惜啊QAQ
考场上写的代码略丑,请大家见谅。如果大家还有什么疑问或者我的程序错了,可以评论在下面。
我会尽心回复大家的评论的qwq!

#include<cstdio>
typedef long long ll;
const int MAXN = 100000;
const ll INF = (1LL<<60);
struct edge{
	int to;
	edge *nxt;
}edges[MAXN + 5], *adj[MAXN + 5], *ecnt=&edges[0];
void addedge(int u, int v) {
	edge *p=(++ecnt);
	p->to = v, p->nxt = adj[u], adj[u] = p;
}
int n, L; ll w[MAXN + 5], S;
int fa[MAXN + 5], dep[MAXN + 5], siz[MAXN + 5];
int fir[MAXN + 5], dfn[MAXN + 5], top[MAXN + 5], dcnt = 0;
void dfs1(int rt, int pre) {
	fa[rt] = pre, dep[rt] = dep[pre] + 1, siz[rt] = 1;
	for(edge *p=adj[rt];p!=NULL;p=p->nxt) {
		if( p->to == pre ) continue;
		dfs1(p->to, rt);
		siz[rt] += siz[p->to];
	}
}
void dfs2(int rt, int tp) {
	top[rt] = tp, fir[rt] = (++dcnt), dfn[dcnt] = rt;
	int hvy = 0;
	for(edge *p=adj[rt];p!=NULL;p=p->nxt) {
		if( p->to == fa[rt] ) continue;
		if( hvy == 0 || siz[p->to] > siz[hvy] )
			hvy = p->to;
	}
	if( !hvy ) return ;
	dfs2(hvy, tp);
	for(edge *p=adj[rt];p!=NULL;p=p->nxt)
		if( p->to != fa[rt] && p->to != hvy )
			dfs2(p->to, p->to);
}
struct node{
	int le, ri;
	bool tag;
}tree[4*MAXN + 5];
void Build(int x, int l, int r) {
	tree[x].le = l, tree[x].ri = r;
	if( l == r ) return ;
	int Mid = (l + r) >> 1;
	Build(x<<1, l, Mid);
	Build(x<<1|1, Mid+1, r);
}
void Modify(int x, int l, int r) {
	if( l > tree[x].ri || r < tree[x].le )
		return ;
	if( l <= tree[x].le && tree[x].ri <= r ) {
		tree[x].tag = true;
		return ;
	}
	if( tree[x].tag ) return ;
	Modify(x<<1, l, r);
	Modify(x<<1|1, l, r);
}
bool Query(int x, int pos) {
	if( pos > tree[x].ri || pos < tree[x].le )
		return false;
	if( pos == tree[x].le && pos == tree[x].ri ) return tree[x].tag;
	if( tree[x].tag ) return true;
	else return Query(x<<1, pos) || Query(x<<1|1, pos);
}
void TModify(int u, int v) {
	while( top[u] != top[v] ) {
		if( dep[top[u]] > dep[top[v]] ) {
			Modify(1, fir[top[u]], fir[u]);
			u = fa[top[u]];
		}
		else {
			Modify(1, fir[top[v]], fir[v]);
			v = fa[top[v]];
		}
	}
	if( dep[u] > dep[v] )
		Modify(1, fir[v], fir[u]);
	else Modify(1, fir[u], fir[v]);
}
int fa1[MAXN + 5][25];
ll sum[MAXN + 5][25];
void dfs(int rt) {
	for(int i=1;i<20;i++) {
		fa1[rt][i] = fa1[fa1[rt][i-1]][i-1];
		if( sum[rt][i-1] != INF && sum[fa1[rt][i-1]][i-1] != INF )
			sum[rt][i] = sum[rt][i-1] + sum[fa1[rt][i-1]][i-1];
		else sum[rt][i] = INF;
	}
	for(edge *p=adj[rt];p!=NULL;p=p->nxt) {
		fa1[p->to][0] = rt;
		sum[p->to][0] = w[rt];
		dfs(p->to);
	}
}
int UpTo(int u, ll lim, int dep) {
	dep--; lim -= w[u];
    for(int i=19;i>=0;i--) {
        if( sum[u][i] <= lim && dep >= (1<<i) ) {
            lim -= sum[u][i];
            dep -= (1<<i);
            u = fa1[u][i];
        }
    }
    return u;
}
int ans = 0;
void dfs3(int rt) {
	for(edge *p=adj[rt];p!=NULL;p=p->nxt)
		dfs3(p->to);
	if( !Query(1, fir[rt]) ) {
		TModify(rt, UpTo(rt, S, L));
		ans++;
	}
}
int main() {
	scanf("%d%d%I64d", &n, &L, &S);
	for(int i=1;i<=n;i++)
		scanf("%I64d", &w[i]);
	for(int i=2;i<=n;i++) {
		int p;
		scanf("%d", &p);
		addedge(p, i);
	}
	for(int i=1;i<=n;i++) {
		if( w[i] > S ) {
			printf("%d\n", -1);
			return 0;
		}
	}
	for(int i=0;i<20;i++) {
        sum[0][i] = (1LL<<60);
        fa1[0][i] = 0;
    }
    sum[1][0] = (1LL<<60);
	dfs1(1, 0); dfs2(1, 1); Build(1, 1, n); dfs(1); dfs3(1);
	printf("%d\n", ans);
}

@END@

就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。

### Codeforces Problem 976C Solution in Python For solving problem 976C on Codeforces using Python, efficiency becomes a critical factor due to strict time limits aimed at distinguishing between efficient and less efficient solutions[^1]. Given these constraints, it is advisable to focus on optimizing algorithms and choosing appropriate data structures. The provided code snippet offers insight into handling string manipulation problems efficiently by customizing comparison logic for sorting elements based on specific criteria[^2]. However, for addressing problem 976C specifically, which involves determining the winner ('A' or 'B') based on frequency counts within given inputs, one can adapt similar principles of optimization but tailored towards counting occurrences directly as shown below: ```python from collections import Counter def determine_winner(): for _ in range(int(input())): count_map = Counter(input().strip()) result = "A" if count_map['A'] > count_map['B'] else "B" print(result) determine_winner() ``` This approach leverages `Counter` from Python’s built-in `collections` module to quickly tally up instances of 'A' versus 'B'. By iterating over multiple test cases through a loop defined by user input, this method ensures that comparisons are made accurately while maintaining performance standards required under tight computational resources[^3]. To further enhance execution speed when working with Python, consider submitting codes via platforms like PyPy instead of traditional interpreters whenever possible since they offer better runtime efficiencies especially important during competitive programming contests where milliseconds matter significantly.
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值