CSP-S 2023 种树-题解

CSP-S 2023 种树-题解

闲话

Mark.Down看错题面了,我一直以为树是倒着长的。

题目描述

给定一棵树,每天可以选择一个与已种树的地块相连的地块种树,每棵树每天会长\(max(1,c_i\times x+b_i)\)米(\(x\)代表从任务开始第一天的天数),问最少多少天可以使\(\forall i\in n,Height_i\ge a_i\)

题目分析

直接求解不好做,那我们可以考虑二分答案,求解转判定。

二分答案的单调性证明:可知每天树都会向上生长,所以只要在一个小的天数能完成任务,那么使用同样的策略可以直接种完然后等待即可,一定也是满足要求的。

那么我们考虑对答案进行判定,我们可以根据极限时间求出在当前极限时间下,每棵树最晚在那一天进行种植,对于每个有子节点的节点,他必须在子节点的种植极限时间之前种植,也就是说\(Limit_u=min(Limit_v-1,Limit_u)\),对于\(Limit\)的求解,我们也可以二分答案,看从那天开始生长直到限制时间能满足该棵树的要求。求解所有\(Limit\)之后,我们只需要将其排序,看能否有一个合法的序列完成种植即可,而不合法的情况则是在排序后出现\(Limit_i<i\)的情况,也就说明完全无法形成这样的一个序列。

优化

看起来上面的思路很美好,但是这个题需要卡常,一些奇怪的卡常点:1)如果当前节点不是根节点并且\(Limit\le 1\)那么可以直接返回,不需要排序了。2)把变量定义在外面。

Code

/*
 * @Author: Ehundategh
 * @Date: 2023-10-23 11:50:50
 * @FilePath: \Code\CSP-S\tree.cpp
 * @Description: You Steal,I kill
 */
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 100001
#define Min(a,b) if(a<b) b=a
using namespace std;
#define long_num __int128
int Head[MAXN],Total,n,In1,In2;
long long A[MAXN],B[MAXN],C[MAXN],LimitT[MAXN],EndT[MAXN],EndV[MAXN];/*max(B[i]+x*C[i],1)*/
template <class T>
void Read(T &x){
    x=0;
    bool f=false;
    char c=getchar();
    while(c<'0' or c>'9'){
        if(c=='-')f=true;
        c=getchar();
    }
    while('0'<=c and c<='9'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    if(f)x=-x;
}
struct edge{
    int St,Ed,Next;
}Edge[MAXN<<1];
inline void Edge_Add(int St,int Ed){
    Edge[++Total]={St,Ed,Head[St]};
    Head[St]=Total;
}
long_num Height;
inline bool Check(int Now,int St,int Ed){
    Height=0;
    if(St>EndT[Now]){
        Height=Ed-St+1;
    }
    else if(St<=EndT[Now]&&Ed>=EndT[Now]){
        Height+=Ed-EndT[Now];
        Height+=((B[Now]+C[Now]*(long_num)St+EndV[Now])*(EndT[Now]-(long_num)St+1))>>1;
    }
    else{
        Height=((B[Now]+C[Now]*(long_num)St+B[Now]+C[Now]*(long_num)Ed)*(long_num)(Ed-St+1))>>1;
    }
    return Height>=A[Now];
}
inline int Tackle(int Now,int Limit){
    int L=1,R=Limit;
    while(L<R){
        int Mid=(L+R)>>1;
        if(Mid==L) Mid++;
        if(Check(Now,Mid,Limit)) L=Mid;
        else R=Mid-1;
    }
    return L;
}
inline void Take(int Now,int From,int Limit){
    LimitT[Now]=Tackle(Now,Limit);
    if(LimitT[Now]<=0||(Now!=1&&LimitT[Now]<=1)) return;
    for(int i=Head[Now];i;i=Edge[i].Next){
        int To=Edge[i].Ed;
        if(To==From) continue;
        Take(To,Now,Limit);
        Min(LimitT[To]-1,LimitT[Now]);
    }
    if(LimitT[Now]<=0||(Now!=1&&LimitT[Now]<=1)) return;
}
inline void Init(){
    for(int i=1;i<=n;i++){
        if(C[i]<0) EndT[i]=(B[i]-1)/-C[i],EndV[i]=(B[i]+EndT[i]*C[i]);
        else EndT[i]=1<<30;
    }
    return;
}
inline bool Judge(int Mid){
    Take(1,1,Mid);
    sort(LimitT+1,LimitT+n+1);
    for(int i=1;i<=n;i++){
        if(LimitT[i]<i) return false;
    }
    return true;
}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) Read(A[i]),Read(B[i]),Read(C[i]);
    for(int i=1;i<n;i++){
        Read(In1);Read(In2);
        Edge_Add(In1,In2);
        Edge_Add(In2,In1);
    }
    Init();
    int L=1,R=1<<30;
    while(L<R){
        int Mid=(L+R)>>1;
        if(R==Mid) Mid--;
        if(Judge(Mid)) R=Mid;
        else L=Mid+1;
    }
    printf("%d",L);
    return 0;
}
posted @ 2023-10-23 17:05  Ehundategh  阅读(506)  评论(0编辑  收藏  举报