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;
}