P9755 [CSP-S 2023] 种树

P9755 [CSP-S 2023] 种树

[CSP-S 2023] 种树

题目描述

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

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

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

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

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

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

输入格式

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

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

接下来 n1 行:每行包含两个正整数 ui,vi,表示一条连接地块 uivi 的道路。

输出格式

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

数据范围

对于所有测试数据有:1n105,1ai1018,1bi109,0|ci|109,1ui,vin。保证存在方案能在 109 天内完成任务。

Solution:

说句闲话

也是怀揣着勇气打开了一年前把我干死的题,开始以为c,b都是正整数,max(1,cx+b)就是一个笑话,然后我在教室yy了五分钟就认为自己把这题秒了

直到我第二次上机房看到“0|ci|109” 天都塌了

首先我们不难想到二分答案,然后考虑对于每一个答案计算一个t[i],表示在该答案下,第i棵树最晚要在第t[i]天开始种植才能使得在ans天时满足题目的要求

然后我们思考一下怎么求这个天数:
设h(b,c,k)表示在从第1天到第k天,b=b,c=c的一颗树能长多高
形式化的:h=x=1kmax(1,cx+b)
我们会发现,这个式子就是求一个形如y=max(1,cx+b)这样的分段一次函数的和。
那么h函数显然满足前缀和的性质,也就是说,一棵树在[L,R]段时间内生长的高度就是h(R)-h(L-1);
然后我们会发现,对于这个分段一次函数,我们只需要求出他们的交点然后O(1)分别计算左右两边的值就好了

到这里这题就做完一半了,接下来的问题就是:在一颗树上,每个时刻只能对一个相父亲点被染过色的节点染色,编号为i的点的最晚染色时间为t[i],求是否有可行方案

这部分竟然可以直接贪心:
我们思考一个问题,我们假设显然即将要对x节点染色,下一个染色的节点是y,且t[x]<t[y];

那么我们将染x节点的路径x->1和染y所需的路径y->1画出来我们就会发现:
只要执行完了这两次操作,不论x,y哪一个先染,到后面那个点染完时,这两次操作过后的图是一样的。也就是说,完成这两个任务的最后时间是一样的,然而t[x]<t[y]显然x要比y的时间更紧迫,所以外面应该让x先染,否则就不保证check()函数的最优性了

然后还要注意的是h函数以及所有与h函数有关的值域在[(1e9)3,(1e9)3]可能把long long 爆掉,所以要开__int128,还有就是由于我们多次调用了h()函数和函数,所以我们最好给他们套一个inline,不然很容易100->55

然后这题就欢乐的做完了

Code

#include<bits/stdc++.h>
typedef __int128 ll;
const int N=1e5+5;
using namespace std;
int n;
long long a[N];
int b[N],c[N],q[N],t[N];
int fa[N],vis[N];
int e_cnt;
int head[N];
struct Edge{
int to,nxt;
}e[N<<1];
inline void add(int x,int y)
{
e[++e_cnt]=(Edge){y,head[x]};
head[x]=e_cnt;
}
inline void write(ll x)
{
return;
vector<int> a;
while(x)
{
a.push_back(x%10);
x/=10;
}
for(int i=a.size()-1;~i;i--){cout<<a[i];}
}
inline ll sum(ll x)
{
return ((x+1)*x)/2;
}
const __int128 one=1;
inline ll h(ll b,ll c,ll R)
{
ll L=max(one,min(R+1,(ll)ceil(1.0*(1-b)/c)));
ll res=0;
if(c<0){return sum(L-1)*c+(L-1)*b+(R-L+1);}
if(c==0){return b*R;}
if(c>0){return (sum(R)-sum(L-1))*c+(R-L+1)*b+(L-1);}
}
inline void dfs(int x,int ff)
{
fa[x]=ff;
for(int i=head[x],to;i;i=e[i].nxt)
{
to=e[i].to;
if(to==fa[x])continue;
dfs(to,x);
}
}
inline int get_t(int x,ll val)
{
int l=1,r=n,ans=-1;
while(l<=r)
{
int mid=l+r>>1;
if(val-h(b[x],c[x],mid-1)>=a[x])
{
l=mid+1;
ans=mid;
}
else
{
r=mid-1;
}
}
return ans;
}
inline void init()
{
for(int i=1;i<=n;i++){vis[i]=0;}
vis[0]=1;
}
inline bool cmp(int x,int y)
{
return t[x]<t[y];
}
inline void update(int x,int &res)
{
if(vis[x])return;
vis[x]=1;
res++;
update(fa[x],res);
}
inline bool check(int k)
{
for(int i=1;i<=n;i++)
{
q[i]=i;
ll val=h(b[i],c[i],k);
if(val<a[i])return 0;
t[i]=get_t(i,val);
}
sort(q+1,q+1+n,cmp);
int qwq=0;
for(int i=1;i<=n;i++)
{
update(q[i],qwq);
if(qwq>t[q[i]])return 0;
}
return 1;
}
inline void work()
{
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%lld%d%d",&a[i],&b[i],&c[i]);
}
for(int i=1,x,y;i<n;i++)
{
scanf("%lld%lld",&x,&y);
add(x,y);add(y,x);
}
dfs(1,0);
long long l=n,r=1e9,ans=1e9;
while(l<=r)
{
int mid=l+r>>1;
init();
if(check(mid))
{
r=mid-1;
ans=mid;
}
else
{
l=mid+1;
}
}
printf("%lld\n",ans);
}
#undef int
int main()
{
//freopen("P9755_2.in","r",stdin);freopen("P9755.out","w",stdout);
work();
return 0;
}
posted @   liuboom  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示