[CSP-S 2023] 种树 题解

[CSP-S 2023] 种树

题目描述

你是一个森林养护员,有一天,你接到了一个任务:在一片森林内的地块上种树,并养护至树木长到指定的高度。

森林的地图有 n n n 片地块,其中 1 1 1 号地块连接森林的入口。共有 n − 1 n-1 n1 条道路连接这些地块,使得每片地块都能通过道路互相到达。最开始,每片地块上都没有树木。

你的目标是:在每片地块上均种植一棵树木,并使得 i i i 号地块上的树的高度生长到不低于 a i a_i ai 米。

你每天可以选择一个未种树且与某个已种树的地块直接邻接即通过单条道路相连)的地块,种一棵高度为 0 0 0 米的树。如果所有地块均已种过树,则你当天不进行任何操作。特别地,第 1 1 1 天你只能在 1 1 1 号空地种树。

对每个地块而言,从该地块被种下树的当天开始,该地块上的树每天都会生长一定的高度。由于气候和土壤条件不同,在第 x x x 天, i i i 号地块上的树会长高 max ⁡ ( b i + x × c i , 1 ) \max(b_i + x \times c_i, 1) max(bi+x×ci,1) 米。注意这里的 x x x 是从整个任务的第一天,而非种下这棵树的第一天开始计算。

你想知道:最少需要多少天能够完成你的任务?

输入格式

输入的第一行包含一个正整数 n n n,表示森林的地块数量。

接下来 n n n 行:每行包含三个整数 a i , b i , c i a_i, b_i, c_i ai,bi,ci,分别描述一片地块,含义如题目描述中所述。

接下来 n − 1 n-1 n1 行:每行包含两个正整数 u i , v i u_i, v_i ui,vi,表示一条连接地块 u i u_i ui v i v_i vi 的道路。

输出格式

输出一行仅包含一个正整数,表示完成任务所需的最少天数。

样例 #1

样例输入 #1

4
12 1 1
2 4 -1
10 3 0
7 10 -2
1 2
1 3
3 4

样例输出 #1

5

提示

【样例 1 解释】

1 1 1 天:在地块 1 1 1 种树,地块 1 1 1 的树木长高至 2 2 2 米。

2 2 2 天:在地块 3 3 3 种树,地块 1 , 3 1, 3 1,3 的树木分别长高至 5 , 3 5, 3 5,3 米。

3 3 3 天:在地块 4 4 4 种树,地块 1 , 3 , 4 1, 3, 4 1,3,4 的树木分别长高至 9 , 6 , 4 9, 6, 4 9,6,4 米。

4 4 4 天:在地块 2 2 2 种树,地块 1 , 2 , 3 , 4 1, 2, 3, 4 1,2,3,4 的树木分别长高至 14 , 1 , 9 , 6 14, 1, 9, 6 14,1,9,6 米。

5 5 5 天:地块 1 , 2 , 3 , 4 1, 2, 3, 4 1,2,3,4 的树木分别长高至 20 , 2 , 12 , 7 20, 2, 12, 7 20,2,12,7 米。

【样例 2】

见选手目录下的 tree/tree2.intree/tree2.ans

【样例 3】

见选手目录下的 tree/tree3.intree/tree3.ans

【样例 4】

见选手目录下的 tree/tree4.intree/tree4.ans

【数据范围】

对于所有测试数据有: 1 ≤ n ≤ 1 0 5 , 1 ≤ a i ≤ 1 0 18 , 1 ≤ b i ≤ 1 0 9 , 0 ≤ ∣ c i ∣ ≤ 1 0 9 , 1 ≤ u i , v i ≤ n 1 ≤ n ≤ 10^5,1 ≤ a_i ≤ 10^{18}, 1 ≤ b_i ≤ 10^9,0 ≤ |c_i| ≤ 10^9, 1 ≤ u_i, v_i ≤ n 1n1051ai1018,1bi1090ci109,1ui,vin。保证存在方案能在 1 0 9 10^9 109 天内完成任务。

T4

特殊性质 A:对于所有 1 ≤ i ≤ n 1 ≤ i ≤ n 1in,均有 c i = 0 c_i = 0 ci=0

特殊性质 B:对于所有 1 ≤ i < n 1 ≤ i < n 1i<n,均有 u i = i , v i = i + 1 u_i = i,v_i = i + 1 ui=ivi=i+1

特殊性质 C:与任何地块直接相连的道路均不超过 2 2 2 条;

特殊性质 D:对于所有 1 ≤ i < n 1 ≤ i < n 1i<n,均有 u i = 1 u_i = 1 ui=1


考场上写了性质 A A A 和性质 B B B,虽然都写挂了,不过确实离正解不远了,所以在讲正解之前先把这两个性质讲一下。

性质 A

题目中告诉我们 c i = 0 c_i=0 ci=0 ,这说明烦人的一次函数不见了,进一步的,我们可以直接算出每一个点需要多少的生长时间。求出来后,仿效P4437的贪心结论,全局中一个最大生长时间的点(设为 x x x )一定会在他父亲被选完后立即被选,我们不妨把 x x x f a [ x ] fa[x] fa[x] 缩成一个点,点权为 m a x ( t [ x ] + s i z e [ f a [ x ] ] , t [ f a [ x ] ] ) max(t[x]+size[fa[x]],t[fa[x]]) max(t[x]+size[fa[x]],t[fa[x]]) s i z e [ x ] size[x] size[x] 表示 x x x 这个点缩完点后的集合大小, + s i z e [ f a [ x ] ] +size[fa[x]] +size[fa[x]] 的含义是表明在父亲所在集合都种完树后再在 x x x 上种树 ,考场上就是这里写挂了。。),这样我们开一个堆,一直合并即可。时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn) ,靠这个思路就可以把整道题做完了。

for(int i=2;i<=n;i++)
q.push(make_pair(t[i],i));
while(!q.empty())
{
	int x=q.top().second;
	q.pop();
	if(flag[x])
	continue;
	flag[x]=1;//注意每个点用完以后打上标记,防止一个点重复合并使答案异常化。
	t[get(F[x])]=max(t[get(F[x])],t[x]+siz[get(F[x])]);
	fa[x]=get(F[x]);
	siz[get(F[x])]+=siz[x];
	if(get(F[x])!=1)
	q.push(make_pair(t[get(F[x])],get(F[x])));
}
printf("%lld",t[1]);

不过这一步在赛后发现可以优化。
优化:
把所有操作整体考虑,不难发现如果一个点 x x x 被选为全局最大生长时间的点时,之后被选的点实际上就是 f a [ x ] fa[x] fa[x] ,因为合并后 f a [ x ] fa[x] fa[x] 的点权一定大于 x x x 的点权,而 x x x 的点权又是之前一步最大的,所以 f a [ x ] fa[x] fa[x] 的点权一定是最大的。这样一直往上跳,直到之前用过的点。那么我们把所有点的价值计算出来之后从大到小排个序,从前往后访问数组,如果一个点还未加入操作序列,就把他和他的所有未被标记的祖先(一定是一段连续的链,所以往上跳的过程中遇到标记过的直接 b r e a k break break 来保证复杂度)加入操作序列,注意是从上往下加。
设点 x x x 在操作序列中的位置是 s[x] ,最后的 a n s = max ⁡ i = 1 n s [ i ] + t [ i ] − 1 ans=\max\limits_{i=1}^ns[i]+t[i]-1 ans=i=1maxns[i]+t[i]1
时间复杂度为 O ( n ) O(n) O(n)

另一种证明方式:
我们考虑不经过缩点的过程,直接证明上面贪心步骤的正确性。
t [ x ] > t [ y ] t[x]>t[y] t[x]>t[y] ,则有 s [ x ] < s [ y ] s[x]<s[y] s[x]<s[y]
用微扰排序的思想,我们尝试证明 s [ x ] > s [ y ] s[x]>s[y] s[x]>s[y] 的安排比 s [ x ] < s [ y ] s[x]<s[y] s[x]<s[y] 一定不优。
x x x 的前驱的个数为 n u m x num_x numx y y y 的前驱的个数为 n u m y num_y numy
s [ x ] < s [ y ] s[x]<s[y] s[x]<s[y] ,则 x , y x,y x,y 对答案的贡献为 a n s 1 = m a x ( n u m x + t [ x ] , n u m x + 1 + n u m y + t [ y ] ) ans1=max(num_x+t[x],num_x+1+num_y+t[y]) ans1=max(numx+t[x],numx+1+numy+t[y])
s [ x ] > s [ y ] s[x]>s[y] s[x]>s[y] ,则 x , y x,y x,y 对答案的贡献为 a n s 2 = m a x ( n u m y + t [ y ] , n u m y + 1 + n u m x + t [ x ] ) = n u m y + 1 + n u m x + t [ x ] ans2=max(num_y+t[y],num_y+1+num_x+t[x])=num_y+1+num_x+t[x] ans2=max(numy+t[y],numy+1+numx+t[x])=numy+1+numx+t[x]
显然 a n s 1 ≤ a n s 2 ans1\le ans2 ans1ans2 ,所以 s [ x ] < s [ y ] s[x]<s[y] s[x]<s[y] 的决策一定不劣。

性质 B

性质 B B B 告诉我们这是一条链,也就是说我们不需要考虑操作顺序的问题了。我们假设点 i i i 的生长时间区间为 [ L i , R i ] [L_i,R_i] [Li,Ri]
显然 L i L_i Li 是定的,我们需要知道的是最小的 R x R_x Rx 是多少。
发现 b i + x × c i b_i + x \times c_i bi+x×ci 其实是一段等差数列,所以我们可以二分 R i R_i Ri ,用等差数列求和公式来 O ( 1 ) O(1) O(1) 算出生长高度,然后与 a i a_i ai 进行比较来验证 R i R_i Ri 是否合法。但是它是 m a x ( b i + x × c i , 1 ) max(b_i + x \times c_i,1) max(bi+x×ci,1) ,有可能不是一段完整的等差数列,怎么办呢?
开始大力分类讨论:
c i > = 0 c_i>=0 ci>=0 时, a n s = ( b i + L i × c i + b i + R i × c i ) × ( R i − L i + 1 ) / 2 ans=(b_i+L_i\times c_i+b_i+R_i\times c_i)\times(R_i-L_i+1)/2 ans=(bi+Li×ci+bi+Ri×ci)×(RiLi+1)/2
c i < 0 c_i<0 ci<0 时, b i + x × c i ≥ 1 ⇒ x ≤ ⌊ 1 − b i c i ⌋ b_i+x\times c_i\ge1 \Rightarrow x\le\left \lfloor\frac{1-b_i}{c_i}\right\rfloor bi+x×ci1xci1bi 我们可以算出在什么时候取 b i + x × c i b_i+x\times c_i bi+x×ci ,什么时候取 1 1 1
t = ⌊ 1 − b i c i ⌋ t=\left \lfloor\frac{1-b_i}{c_i}\right\rfloor t=ci1bi
\qquad R i < = t R_i<=t Ri<=t 时, a n s = ( b i + L i × c i + b i + R i × c i ) × ( R i − L i + 1 ) / 2 ans=(b_i+L_i\times c_i+b_i+R_i\times c_i)\times(R_i-L_i+1)/2 ans=(bi+Li×ci+bi+Ri×ci)×(RiLi+1)/2
\qquad L i > t L_i>t Li>t 时, a n s = R i − L i + 1 ans=R_i-L_i+1 ans=RiLi+1
\qquad L i < = t L_i<=t Li<=t R i > t R_i>t Ri>t 时, a n s = ( b i + L i × c i + b i + t × c i ) × ( t − L i + 1 ) / 2 + R i − t ans=(b_i+L_i\times c_i+b_i+t\times c_i)\times(t-L_i+1)/2+R_i-t ans=(bi+Li×ci+bi+t×ci)×(tLi+1)/2+Rit
这样我们就可以直接递推加二分的求解出链的情况。

正解

我们发现性质 A A A 是考虑操作序列,不考虑等差数列,性质 B B B 是考虑等差数列,不考虑操作序列,我们尝试把两种做法结合一下。
对于变量,一个点的点权不太好算,再加上答案显然具有单调性,我们考虑二分答案。这样我们可以按照链的做法把每个点需要在第几天之前种下给计算出来,虽然不是生长时间了,但稍微思考一下发现这个点权和生长时间没什么太大区别,我们从小到大排序,然后仿照性质 A A A 的做法求出操作序列,最后验证二分出来的答案是否合法就行。
时间复杂度: O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

#include<bits/stdc++.h>
using namespace std;
int n,head[100010],tot,F[100010];
bool v[100010];
vector<pair<int,int> > q;
struct node
{
	int to,nex;
} edge[200000];
struct node1
{
	long long a,b,c,t;
} a[100010];
void add(int x,int y)
{
	edge[++tot].nex=head[x];
	edge[tot].to=y;
	head[x]=tot;
}
bool Check(int id,int l,int r)
{
	if(a[id].c>=0)
	{
		__int128 h=(__int128)(a[id].b+l*a[id].c+a[id].b+r*a[id].c)*(r-l+1)/2;
		if(h>=a[id].a)
		return 1;
		return 0;
	}
	if(l>(1-a[id].b)/a[id].c)
	{
		if(r-l+1>=a[id].a)
		return 1;
		return 0;
	}
	if(r<=(1-a[id].b)/a[id].c)
	{
		__int128 h=(__int128)(a[id].b+l*a[id].c+a[id].b+r*a[id].c)*(r-l+1)/2;
		if(h>=a[id].a)
		return 1;
		return 0;
	}
	int t=(1-a[id].b)/a[id].c;
	__int128 h=(__int128)(a[id].b+l*a[id].c+a[id].b+t*a[id].c)*(t-l+1)/2+r-t;
	if(h>=a[id].a)
	return 1;
	return 0;
}
int work(int id,int x)
{
	int l=0,r=x;
	while(l+1<r)
	{
		int mid=(l+r)/2;
		if(Check(id,mid,x))
		l=mid;
		else
		r=mid;
	}
	if(Check(id,r,x))
	return r;
	return l;
}
bool check(int x)
{
	q.clear();
	for(int i=1;i<=n;i++)
	{
		int jl=work(i,x);
		if(jl==0)
		return 0;
		q.push_back(make_pair(jl,i));
	}
	sort(q.begin(),q.end());
	memset(v,0,sizeof v);
	v[0]=1;
	int now=0;
	for(int i=0;i<q.size();i++)
	{
		int id=q[i].second;
		while(!v[id])
		{
			now++;
			v[id]=1;
			id=F[id];
		}
		if(now>q[i].first)
		return 0;
	}
	return 1;
}
void dfs(int x,int fa)
{
	F[x]=fa;
	for(int i=head[x];i;i=edge[i].nex)
	{
		if(edge[i].to==fa)
		continue;
		dfs(edge[i].to,x);
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%lld%lld%lld",&a[i].a,&a[i].b,&a[i].c);
	for(int i=1;i<n;i++)
	{
		int U,V;
		scanf("%d%d",&U,&V);
		add(U,V);
		add(V,U);
	}
	dfs(1,0);
	int l=1,r=1e9;
	while(l+1<r)
	{
		int mid=(l+r)/2;
		if(check(mid))
		r=mid;
		else
		l=mid;
	}
	if(check(l))
	printf("%d",l);
	else
	printf("%d",r);
	return 0;
}

总结:一道题没有思路的时候,先去想想性质,或许会有不错的发现。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
csp-s是中国信息学奥林匹克竞赛的高中组别赛事,而第二轮则是指整个比赛分为两轮进行。2023年的csp-s第二轮将继续推动学生们的计算机科学学习和技能发展。 第二轮比赛通常更加复杂和挑战性,要求参赛者具备扎实的计算机基础知识和技能。比赛题目涵盖了算法、数据结构、编程语言等多个方面,旨在考验学生的思维逻辑和解决问题的能力。 对于参赛者而言,csp-s第二轮2023一个展示自己技能的机会,也是一个不断学习和成长的过程。比赛的形式为在线答题,参赛者需要在规定的时间内完成一系列的编程题目。在这个过程中,他们将面对一定的时间压力和竞争压力,需要在有限的时间内迅速思考、捕捉问题的本质,并找到最佳的解决方法。 除了对个人技能的考察外,csp-s第二轮还鼓励参赛者之间的合作和交流。在解题过程中,可以借鉴他人的思路和解法,从而共同进步。这也是csp-s比赛独特的地方之一,它不仅是个人能力的竞技场,更是一个团队合作的平台。 对于学校和教育机构而言,csp-s第二轮2023一个促进计算机科学教育的机会。通过参与这样的比赛,学生们可以接触到实际的编程题目,提高他们的实践能力和解决问题的能力。同时,教师们也可以通过这个平台了解学生的学习情况和技能水平,为他们提供更加有针对性的教学。 总而言之,csp-s第二轮2023将是一场精彩的比赛,为学生们提供展示自己技能和学习成果的机会。在参与比赛的过程中,他们将不断锻炼和提高自己的计算机科学技能,并与其他优秀的学生进行交流和合作。这将有助于他们未来的学习和职业发展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值