P9755 [CSP-S 2023] 种树 (二分+贪心)
首先,容易看出单调性,可以对最少天数二分。转为判定性问题后,我们思考如何判定。对于每棵树,都可以从刚种下长到最后一天。我们由此可以写出
-
对于
,直接把最大值去掉。 -
对于
, 一定与 有一个交点。当 时,此时分为三种情况: 当 时, 当 时, 当 ,
至此
对于“求出每个树最晚被种下去的时间”,我们同样可以二分解决。如何做到最优呢?对于此时我们应该思考贪心和动态规划。先思考贪心,若我们以最晚种植时间从小到大排序,然后依次种下每棵树,是否能做到最优?答案是可以的。考虑邻项交换,这时发现,没交换时不能种下,交换后也不能种下;交换后不能种下的交换前可能可以种下。我们只关心现在最急迫的做法的是最优方案之一。
实现方面,我们可以标记沿途路径,在种一棵新的树时暴力跳到已标记的点,计算时间。这样子在种完所有树后,只遍历了每个点一次,复杂度
总复杂度
总结:对于具有二分性质的题目,如何判定一般用贪心或动规。对复杂一点的函数有相应的计算能力。
#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;
}
Buy me a cup of coffee ☕.
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析