P9755 [CSP-S 2023] 种树 题解

简化题意

给定一棵 \(n\) 个节点的树,\(1\) 号节点为根。初始所有节点未种树。

每天可以选择一个未种树且与已种树节点相邻的节点种树(第一天只能种 \(1\) 号节点)。若所有节点已种树,则当天不操作。

对于节点 \(i\),种下后第 \(x\) 天(从第一天算起)高度增加 \(\max(b_i + x \times c_i, 1)\)。

求使所有节点 \(i\) 的树高不低于 \(a_i\) 的最少天数。

做法

首先看完题目,答案显然是存在单调性的,\(i\) 天可以达成目标则 \(i+1\) 天也可以达成目标,所以考虑二分答案。

性质A

\(c_i\) 为 \(0\),说明可以算出来每个树需要多久才会长达标,因此我得出每棵树至少要在哪一天种下才会达标(记作 \(ne_i\))。 对于这一棵树,肯定是寻找最小的 \(ne_i\) 优先让它生长,由于需要它的父节点先种下,所以我们从它往上找,直到走到节点 \(1\) 或者是一个种过树的节点,然后在一次种下树即可。最后判断种下的时间是否达标。

点代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100005;
int n;
int a[N],b[N],c[N];
vector<int>g[N];
struct no{
	int id,mst;
}A[N];
bool cmp(no x,no y){
	return x.mst<y.mst;
}
int fa[N];
int st[N],top;
int co[N],tot;
void subA_init(int u,int f){
	fa[u]=f;
	for(auto i:g[u]){
		if(i!=f)subA_init(i,u);
	}
	return;
}
bool check(int x){
	for(int i=1;i<=n;i++){
		A[i]={i,x-(int)ceil(1.0*a[i]/b[i])+1};
//		if(x==6)cout<<A[i].mst<<' ';
		if(A[i].mst<=0)return 0;
		co[i]=0;
	}
	tot=0;
	co[1]=++tot;
	sort(A+1,A+n+1,cmp);
	for(int i=1;i<=n;i++){
		if(co[A[i].id])continue;
		int x=A[i].id;
		while(!co[x]){
			st[++top]=x;
			x=fa[x];
		}
		for(int j=top;j>=1;j--)co[st[j]]=++tot;
		top=0;
	}
	for(int i=1;i<=n;i++)if(co[A[i].id]>A[i].mst)return 0;
	return 1;
}
void subA(){
	subA_init(1,0);
	int l=0,r=1000000000;
	while(l+1<r){
		int mid=(l+r)>>1;
		if(check(mid))r=mid;
		else l=mid;
	}
	cout<<r;
	return;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i]>>b[i]>>c[i];
	int Sum1=0,Sum2=0,Sum3=0;
	for(int i=1;i<n;i++){
		int u,v;cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
		if(v!=u+1)Sum2++;
		if(u!=1)Sum3++;
	}
	for(int i=1;i<=n;i++){
		if(c[i])Sum1++;
	}
	if(!Sum1)subA();
//	if(!Sum2)subB();
//	if(!Sum3)subD();
	return 0;
}

性质B

给的是一条链,也就是可以种树的顺序确定了,我们可以写一个函数判断第 \(i\) 棵树从 \(l\) 时刻生长到 \(r\) 时刻可以长高多少,分三种情况讨论 :

代码
__int128 checkB(int i,__int128 l,__int128 r){
	if(c[i]>=0)return (2*b[i]+c[i]*l+c[i]*r)*(r-l+1)/2;//直接算
	__int128 t=(1-b[i])/c[i];
	if(l>t)return r-l+1;//全是1
	if(r<t)return (2*b[i]+c[i]*l+c[i]*r)*(r-l+1)/2;//全不是1
	return (2*b[i]+c[i]*l+c[i]*t)*(t-l+1)/2+r-t;//各一半
}

由于生长高度随时间变化而变化,所以是一个梯形,直接求面积即可,然后 \(t\) 则是根据 \(b_i-c_i \times t >=1\) 转换而成。

正解

有了这两个公式就可以直接得到正解,将两个部分分拼到一起即可,用二分得到 \(ne\) 数组。

代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100005;
int n;
int a[N],b[N],c[N];
vector<int>g[N];
__int128 get(int i,__int128 l,__int128 r){
    if(c[i]>=0)return (b[i]*2+c[i]*l+c[i]*r)*(r-l+1)/2;
    __int128 t=(1-b[i])/c[i];//变成1的前一天
    if(t<l)return r-l+1;
    else if(t>=r)return (b[i]*2+c[i]*l+c[i]*r)*(r-l+1)/2;
    return (b[i]*2+c[i]*l+c[i]*t)*(t-l+1)/2+r-t;
}
int sta(int i,int x){
    int l=1,r=x+1;
    while(l+1<r){
        int mid=(l+r)>>1;
        if(get(i,mid,x)>=a[i])l=mid;
        else r=mid;
    }
    return l;
}
struct no{
    int id,ne;
}q[N];
bool cmp(no x,no y){
    return x.ne<y.ne;
}
int co[N];
int st[N],top,tot;
int nd[N];
int fa[N];
void dfs(int u,int f){
    fa[u]=f;
    for(auto i:g[u]){
        if(i==f)continue;
        dfs(i,u);
    }
    return;
}
bool check(int x){
    for(int i=1;i<=n;i++){
        if(get(i,1,x)<a[i])return 0;//一定要加这句话,不然就是40pts
        co[i]=0;nd[i]=sta(i,x);
        q[i]={i,nd[i]};
    }
    sort(q+1,q+n+1,cmp);
    tot=0;
    co[1]=++tot;
    for(int i=1;i<=n;i++){
        int pos=q[i].id;
        if(co[pos])continue;
        top=0;
        while(!co[pos]){
            st[++top]=pos;
            pos=fa[pos];
        }
        for(int j=top;j>=1;j--){
            co[st[j]]=++tot;
            if(co[st[j]]>nd[st[j]])return 0;
        }
    }
    return 1;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i]>>b[i]>>c[i];
    for(int i=1;i<n;i++){
        int u,v;cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1,0);
    int l=0,r=1000000000;
    while(l+1<r){
        int mid=(l+r)>>1;
        if(check(mid))r=mid;
        else l=mid;
    }
    cout<<r;
    return 0;
}
posted @ 2026-09-17 13:25  tangkaiming  阅读(19)  评论(0)    收藏  举报