P9755 [CSP-S 2023] 种树 (二分+贪心)
首先,容易看出单调性,可以对最少天数二分。转为判定性问题后,我们思考如何判定。对于每棵树,都可以从刚种下长到最后一天。我们由此可以写出 \(calc(i,l,r)\) 表示第 \(i\) 棵树从第 \(l\) 天长到第 \(r\) 天的高度。
\(calc(i,l,r)=\sum\limits_{i=l}^r\max(1,b_i+i\times c_i)\)
-
对于 \(c_i>0\),直接把最大值去掉。
\(calc(i,l,r)=\sum\limits_{i=l}^rb_i+i\times c_i=(r-l+1)b_i+(l+r)(r-l+1)c_i\)
-
对于 \(c_i<0\),\(y(x)=b_i+c_i\times x\) 一定与 \(1\) 有一个交点。当 \(x=\lfloor\frac{1-b_i}{c_i}\rfloor\) 时,此时分为三种情况:
\(\bullet\) 当 \(x<l\) 时,\(calc(i,l,r)=r-l+1\)
\(\bullet\) 当 \(x>r\) 时,\(calc(i,l,r)=(r-l+1)b_i+(l+r)(r-l+1)c_i\)
\(\bullet\) 当 \(x\in[l,r]\),\(calc(i,l,r)=(x-l+1)b_i+(l+x)(x-l+1)c_i+r-x\)
至此 \(calc(i,l,r)\) 就写完了。我们该如何判定呢?对于一个固定的结束时间,我们自然能想到求出每个树最晚被种下去的时间,如果最后最优方案下有一棵树的种植时间晚于最晚种植时间,那么就不合法。
对于“求出每个树最晚被种下去的时间”,我们同样可以二分解决。如何做到最优呢?对于此时我们应该思考贪心和动态规划。先思考贪心,若我们以最晚种植时间从小到大排序,然后依次种下每棵树,是否能做到最优?答案是可以的。考虑邻项交换,这时发现,没交换时不能种下,交换后也不能种下;交换后不能种下的交换前可能可以种下。我们只关心现在最急迫的做法的是最优方案之一。
实现方面,我们可以标记沿途路径,在种一棵新的树时暴力跳到已标记的点,计算时间。这样子在种完所有树后,只遍历了每个点一次,复杂度 \(O(n)\)。
总复杂度 \(O(n\log n\log V)\)。
总结:对于具有二分性质的题目,如何判定一般用贪心或动规。对复杂一点的函数有相应的计算能力。
#include <bits/stdc++.h>
typedef long long ll;
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(!isdigit(c)) {
if(c == '-') f = -1;
c = getchar();
}
while(isdigit(c)) {
x = (x << 3) + (x << 1) + (c - '0');
c = getchar();
}
return x * f;
}
ll ans;
ll n, cnt;
ll a[100010], b[100010], c[100010];
struct node {
int to, nxt;
} e[200010];
int h[100010];
void add(int u, int v) {
e[++cnt].to = v;
e[cnt].nxt = h[u];
h[u] = cnt;
}
__int128 calc(ll x, __int128 l, __int128 r) {
if(c[x] >= 0) {
return (r - l + 1) * b[x] + (l + r) * (r - l + 1) / 2 * c[x];
}
__int128 imax = (1 - b[x]) / c[x];
if(imax < l) {
return r - l + 1;
}
else if(imax > r) {
return (r - l + 1) * b[x] + (l + r) * (r - l + 1) / 2 * c[x];
}
return (imax - l + 1) * b[x] + (l + imax) * (imax - l + 1) / 2 * c[x] + r - imax;
}
struct tim{
int x;
} t[100010];
int id[100010];
bool cmp(int a, int b) {
return t[a].x < t[b].x;
}
int st[100010], top;
int fa[100010], vis[100010];
bool check(ll x) {
for(int i = 1; i <= n; i++) {
if(calc(i, 1, x) < a[i]) return 0;
int l = 1, r = n, ret = n;
while(l <= r) {
int mid = (l + r) >> 1;
if(calc(i, mid, x) >= a[i]) l = mid + 1, ret = mid;
else r = mid - 1;
}
vis[i] = 0;
id[i] = i;
t[i] = {ret};
}
std::sort(id + 1, id + n + 1, cmp);
top = 0;
for(int i = 1, f = 0; i <= n; i++) {
int now = id[i];
while(!vis[now]) st[++top] = now, vis[now] = 1, now = fa[now];
while(top) {
++f;
if(t[st[top]].x < f) {
return 0;
}
top--;
}
}
return 1;
}
void dfs(int u, int f) {
for(int i = h[u]; i; i = e[i].nxt) {
int v = e[i].to;
if(v == f) continue;
fa[v] = u;
dfs(v, u);
}
}
void Solve() {
n = read();
for(int i = 1; i <= n; i++) {
a[i] = read(), b[i] = read(), c[i] = read();
}
for(int i = 1; i < n; i++) {
int u = read(), v = read();
add(u, v), add(v, u);
}
vis[0] = 1;
dfs(1, 0);
ll l = n, r = 1e9;
while(l <= r) {
ll mid = (l + r) >> 1;
if(check(mid)) r = mid - 1, ans = mid;
else l = mid + 1;
}
std::cout << ans << "\n";
}
int main() {
Solve();
return 0;
}