P9755 [CSP-S 2023] 种树
题目描述
你是一个森林养护员,有一天,你接到了一个任务:在一片森林内的地块上种树,并养护至树木长到指定的高度。
森林的地图有 nnn 片地块,其中 111 号地块连接森林的入口。共有 n−1n-1n−1 条道路连接这些地块,使得每片地块都能通过道路互相到达。最开始,每片地块上都没有树木。
你的目标是:在每片地块上均种植一棵树木,并使得 iii 号地块上的树的高度生长到不低于 aia_iai 米。
你每天可以选择一个未种树且与某个已种树的地块直接邻接(即通过单条道路相连)的地块,种一棵高度为 000 米的树。如果所有地块均已种过树,则你当天不进行任何操作。特别地,第 111 天你只能在 111 号空地种树。
对每个地块而言,从该地块被种下树的当天开始,该地块上的树每天都会生长一定的高度。由于气候和土壤条件不同,在第 xxx 天,iii 号地块上的树会长高 max(bi+x×ci,1)\max(b_i + x \times c_i, 1)max(bi+x×ci,1) 米。注意这里的 xxx 是从整个任务的第一天,而非种下这棵树的第一天开始计算。
你想知道:最少需要多少天能够完成你的任务?
输入格式
输入的第一行包含一个正整数 nnn,表示森林的地块数量。
接下来 nnn 行:每行包含三个整数 ai,bi,cia_i, b_i, c_iai,bi,ci,分别描述一片地块,含义如题目描述中所述。
接下来 n−1n-1n−1 行:每行包含两个正整数 ui,viu_i, v_iui,vi,表示一条连接地块 uiu_iui 和 viv_ivi 的道路。
输出格式
输出一行仅包含一个正整数,表示完成任务所需的最少天数。
输入输出样例 #1
输入 #1
4
12 1 1
2 4 -1
10 3 0
7 10 -2
1 2
1 3
3 4
输出 #1
5
说明/提示
【样例 1 解释】
第 111 天:在地块 111 种树,地块 111 的树木长高至 222 米。
第 222 天:在地块 333 种树,地块 1,31, 31,3 的树木分别长高至 5,35, 35,3 米。
第 333 天:在地块 444 种树,地块 1,3,41, 3, 41,3,4 的树木分别长高至 9,6,49, 6, 49,6,4 米。
第 444 天:在地块 222 种树,地块 1,2,3,41, 2, 3, 41,2,3,4 的树木分别长高至 14,1,9,614, 1, 9, 614,1,9,6 米。
第 555 天:地块 1,2,3,41, 2, 3, 41,2,3,4 的树木分别长高至 20,2,12,720, 2, 12, 720,2,12,7 米。
【样例 2】
见选手目录下的 tree/tree2.in
与 tree/tree2.ans
。
【样例 3】
见选手目录下的 tree/tree3.in
与 tree/tree3.ans
。
【样例 4】
见选手目录下的 tree/tree4.in
与 tree/tree4.ans
。
【数据范围】
对于所有测试数据有:1≤n≤105,1≤ai≤1018,1≤bi≤109,0≤∣ci∣≤109,1≤ui,vi≤n1 ≤ n ≤ 10^5,1 ≤ a_i ≤ 10^{18}, 1 ≤ b_i ≤ 10^9,0 ≤ |c_i| ≤ 10^9, 1 ≤ u_i, v_i ≤ n1≤n≤105,1≤ai≤1018,1≤bi≤109,0≤∣ci∣≤109,1≤ui,vi≤n。保证存在方案能在 10910^9109 天内完成任务。
特殊性质 A:对于所有 1≤i≤n1 ≤ i ≤ n1≤i≤n,均有 ci=0c_i = 0ci=0;
特殊性质 B:对于所有 1≤i<n1 ≤ i < n1≤i<n,均有 ui=iu_i = iui=i、vi=i+1v_i = i + 1vi=i+1;
特殊性质 C:与任何地块直接相连的道路均不超过 222 条;
特殊性质 D:对于所有 1≤i<n1 ≤ i < n1≤i<n,均有 ui=1u_i = 1ui=1。
解题思路
- 资源计算函数 fin
- 该函数计算节点 x 在时间区间 [l, r] 内获取的资源总量。
- 若 c[x] >= 0,资源增长为线性函数,直接计算总和。
- 若 c[x] < 0,资源增长在某个时间点达到最大值后停止增长,需分段计算。
- 二分搜索时间 t
- 通过二分法确定满足所有节点资源需求的最小时间 t。初始范围为 [n, 1e9],逐步缩小范围。
- 验证时间 t 是否可行
- 对于每个节点,计算其在 [1, t] 内能否满足 a[i]。
- 若满足,进一步计算满足 a[i] 的最小时间 ti[i](即最早满足需求的时间)。
- 将所有节点按 ti[i] 排序,模拟资源分配过程,确保每个节点在其 ti[i] 前被访问。
树的遍历与验证 - 使用深度优先搜索(DFS)预处理父节点关系。
- 按 ti[i] 排序后,模拟从根节点到各节点的访问顺序,确保时间约束。
代码解释
#include<bits/stdc++.h>
using namespace std;
#define ___ __int128
const int N=1e5+15;
int b[N],c[N],n,fa[N],stk[N];
long long a[N];
int tot,h[N*2],ne[N*2],to[N*2];
int id[N],ti[N];
bool yon[N];
宏与全局变量
___ 定义为 __int128,用于大数计算避免溢出。
a[i] 是节点资源需求,b[i] 和 c[i] 是资源增长函数的参数。
fa[i] 存储父节点,id 和 ti 用于排序和验证。
yon[i] 标记节点是否被访问过。
void add(int a,int b) {tot++;ne[tot]=h[a];h[a]=tot;to[tot]=b;
}
邻接表添加边
用于构建树的邻接表结构。
bool cmp(int a,int b) {return ti[a]<ti[b];
}
排序比较函数
按节点满足需求的时间 ti[i] 排序。
void dfs(int u,int f) {fa[u]=f;for(int i=h[u];i;i=ne[i]) {int j=to[i];if(j!=f)dfs(j,u);}
}
- DFS预处理父节点
- 从根节点开始遍历树,记录每个节点的父节点。
___ fin(int x,___ l,___ r) {if(c[x]>=0) return (r-l+1)*b[x]+(r-l+1)*(l+r)/2*c[x];___ g=(1-b[x])/c[x];if(g<l) return r-l+1;if(g>r) return (r-l+1)*b[x]+(r-l+1)*(l+r)/2*c[x];return (g-l+1)*b[x]+(g-l+1)*(l+g)/2*c[x]+r-g;
}
- 资源计算函数
- 根据 c[x] 的符号选择计算方式:
- c[x] >= 0:线性增长,直接求和。
- c[x] < 0:分段计算,先线性增长到峰值后停止。
bool check(int t) {for(int i=1;i<=n;i++) {if(fin(i,1,t)<a[i])return 0;else {int l=1,r=n;while(l<r) {int mid=(l+r+1)>>1;if(fin(i,mid,t)>=a[i])l=mid;else r=mid-1;}id[i]=i;ti[i]=l;yon[i]=0;}}sort(id+1,id+1+n,cmp);int js=0;for(int i=1;i<=n;i++) {if(yon[id[i]])continue;stack<int>s1;int x=id[i];while(!yon[x]) {yon[x]=1;s1.push(x);if(fa[x]!=0)x=fa[x];}while(!s1.empty()) {int j=s1.top();s1.pop();js++;if(ti[j]<js)return 0;}}return 1;
}
验证函数
检查每个节点在 [1, t] 内是否满足 a[i]。
对满足的节点,计算其最小满足时间 ti[i]。
按 ti[i] 排序后模拟访问顺序,确保时间约束。
int main() {ios::sync_with_stdio(false),cin.tie(0);cin>>n;int aa,bb;for(int i=1;i<=n;i++)cin>>a[i]>>b[i]>>c[i];for(int i=1;i<n;i++) {cin>>aa>>bb;add(aa,bb);add(bb,aa);}dfs(1,0);int l=n,r=1e9;while(l<r) {int mid=(l+r)>>1;if(check(mid))r=mid;elsel=mid+1;}cout<<l;return 0;
}
- 主函数
- 输入树结构和节点参数。
- 预处理父节点关系。
- 二分搜索最小时间 t。
- 输出结果。
复杂度分析
- 时间复杂度:二分搜索 O(log(1e9)),每次验证 O(n log n),总复杂度 O(n log n log(1e9))。
- 空间复杂度:O(n),存储树结构和中间变量。
详细代码
#include<bits/stdc++.h>
using namespace std;
#define ___ __int128
const int N=1e5+15;
int b[N],c[N],n,fa[N],stk[N];
long long a[N];
int tot,h[N*2],ne[N*2],to[N*2];
int id[N],ti[N];
bool yon[N];
void add(int a,int b)
{tot++;ne[tot]=h[a];h[a]=tot;to[tot]=b;
}
bool cmp(int a,int b)
{return ti[a]<ti[b];
}
void dfs(int u,int f)
{fa[u]=f;for(int i=h[u];i;i=ne[i]){int j=to[i];if(j!=f)dfs(j,u);}
}
___ fin(int x,___ l,___ r)
{if(c[x]>=0) return (r-l+1)*b[x]+(r-l+1)*(l+r)/2*c[x];___ g=(1-b[x])/c[x];if(g<l) return r-l+1;if(g>r) return (r-l+1)*b[x]+(r-l+1)*(l+r)/2*c[x];return (g-l+1)*b[x]+(g-l+1)*(l+g)/2*c[x]+r-g;
}
bool check(int t)
{for(int i=1;i<=n;i++){if(fin(i,1,t)<a[i])return 0;else{int l=1,r=n;while(l<r){int mid=(l+r+1)>>1;if(fin(i,mid,t)>=a[i])l=mid;else r=mid-1;}id[i]=i;ti[i]=l;yon[i]=0;}}sort(id+1,id+1+n,cmp);int js=0;for(int i=1;i<=n;i++){if(yon[id[i]])continue;stack<int>s1;int x=id[i];while(!yon[x]){yon[x]=1;s1.push(x);if(fa[x]!=0)x=fa[x];}while(!s1.empty()){int j=s1.top();s1.pop();js++;if(ti[j]<js)return 0;}}return 1;
}
int main()
{ios::sync_with_stdio(false),cin.tie(0);cin>>n;int aa,bb;for(int i=1;i<=n;i++)cin>>a[i]>>b[i]>>c[i];for(int i=1;i<n;i++){cin>>aa>>bb;add(aa,bb);add(bb,aa);}dfs(1,0);int l=n,r=1e9;while(l<r){int mid=(l+r)>>1;if(check(mid))r=mid;elsel=mid+1;}cout<<l;return 0;
}