个人思路分为几个部分:
( deep[i] 随手验证下)
对于每个答案x记性判断,判断步骤↓:(采用解方程)
(1) b+x∗c<=a? 减掉后面1的部分 解方程
(2) 直接解方程 计算 左端点:最迟种植天数 day[i]
ps:c[i]<0 但若第 x 天 未使得 当日生长高度 <1 ,则对 等差数列计算答案不影响。
一元二次方程:(只考虑等差数列部分)
设开始天数为f(first), 传参 a,b,c,end
则有 [(b+f∗c)+(b+e∗c)]∗(e−f+1)/2>=a ,求解 f 。
那么求解f最小值,把原式变为 ∗∗∗−2∗a=0 ,求解 f 并向下取整,判断有无解
f 最后有2解, 但必有一根无解 or 2根都无解(貌似);最后验证 day[i] 。
#include<iostream> // 4 tree
#include<cstdio> // 1e5
#include<cmath>
#include<cstring>
#include<algorithm>
#define lli long long
#define test cout<<"Test"
using namespace std;
const int N = 100037;
lli a[N], b[N], c[N];
int n, x, y;
struct pot{
int v, nxt;
}E[N<<1];
int head[N], tot = 0;
void Add_edge(int u, int v) {
E[++tot].v = v;
E[tot].nxt = head[u];
head[u] = tot;
}
int dep[N], vis[N];
inline void dfs(int u) {
for(int i=head[u];i;i=E[i].nxt) {
int v = E[i].v;
if(vis[v]) continue;
vis[v] = 1;
dep[v] = dep[u] + 1;
dfs(v);
}
}
int day[N]; // 对于每个点和总天数x,最迟种植第i棵树的时间
inline void dfs2(int u) {
// cout<<"U: "<<u<<"\n";
for(int i=head[u];i;i=E[i].nxt) {
int v = E[i].v;
if(vis[v]) continue;
vis[v] = 1;
dfs2(v);
day[u] = min(day[u], day[v]-1);
// 向父亲压缩范围
}
}
inline bool pd(lli ass, lli fff, lli eend, lli a) {
if(eend<ass || ass<=0 || fff<2*a) return false;
else return true;
// 判断ans 是否可行
}
inline lli fcc(lli b, lli c, lli a, lli e) {
lli aa, bb, cc;
aa = -c;
bb = c-2*b;
cc = (e+1) * (2*b + e*c) - 2*a;
// 方程打开后 对于f的 2次项 1次项 常数项 的系数
lli derta = bb*bb - 4*aa*cc;
// △
double ans1, ans2; // 方程2根
lli fl1, fl2, yz1, yz2;
ans1 = (double) ( -bb + 1.0* sqrt(derta) ) / (2.0*aa);
ans2 = (double) ( -bb - 1.0* sqrt(derta) ) / (2.0*aa);
fl1 = floor(ans1);
fl2 = floor(ans2);// 向下取整后结果
yz1 = (b+e*c + b+fl1*c)*(e-fl1+1);
yz2 = (b+e*c + b+fl2*c)*(e-fl2+1);
// 带回验证区间和
if(!pd(fl1, yz1, e, a) && !pd(fl2, yz2, e, a)) return -1; // 无解
else {
if(pd(fl1, yz1, e, a)) return floor(ans1);
if(pd(fl1, yz1, e, a)) return floor(ans2);
}
return -1;
}
inline bool check(int x) { // 二分总答案
memset(day, 0, sizeof(day));
// cout<<"X: "<<x<<"; "<<endl;
int len1;
for(int i=1;i<=n;++i) {
if(dep[i] > x) return false;
if(c[i] == 0) { // 直接倒退计算,无需等差数列
day[i] = x-ceil(a[i]*1.0 / b[i])+1;
continue;
}
if(b[i]+c[i]*x <= 0) { // c[i] < 0 --> (<1)
len1 = ceil(1.0*(b[i]+c[i])/(-c[i]*1.0)); // 左区间 >1 的天数
if(a[i] <= x-len1) { // 右部分生长高度为1的总长度 都足够
day[i] = len1 + a[i];
continue;
}
day[i] = fcc(b[i], c[i], a[i]-(x-len1), len1); // 减去1的高度,结束天数改为len1
} else day[i] = fcc(b[i], c[i], a[i], x); // 否则直接解
}
// for(int i=1;i<=n;++i) cout<<day[i]<<" ";
// puts("");
memset(vis, 0, sizeof(vis));
vis[1] = 1;
dfs2(1); // 树上不等式压缩
for(int i=1;i<=n;++i) { // 存在无解答案
if(day[i]<=0 || day[i]>x) return false;
}
// cout<<"Day: ";
// for(int i=1;i<=n;++i) cout<<day[i]<<" ";
// 构造序列
sort(day+1, day+1+n); // O(nlogn)
for(int i=1;i<=n;++i) {
if(day[i] < i) {
// 如果轮到第i天种的树,比最迟天数大,同样无解
return false;
}
}
// cout<<"Win!\n";
return true;
}
int main() {
scanf("%d", &n);
for(int i=1;i<=n;++i) scanf("%lld%lld%lld", &a[i], &b[i], &c[i]);
for(int i=1;i<=n-1;++i) {
scanf("%d%d", &x, &y);
Add_edge(x, y);
Add_edge(y, x);
}
dep[1] = vis[1] = 1;
dfs(1);
// cout<<"Dep: ";
// for(int i=1;i<=n;++i) cout<<dep[i]<<" ";
int ll=0, rr=1e9+7, mid;
while(ll < rr) {
mid = (ll + rr) >> 1;
if(check(mid)) rr = mid;
else ll = mid+1;
// puts("");
}
printf("%d\n", ll);
return 0;
}
/*
4
12 1 1
2 4 -1
10 3 0
7 10 -2
1 2
2 3
3 4
: 9
经原样例
修改为链式
压缩前day:
4 2 6 9
压缩后day:
1 2 6 9
ans = 9
*/