Loading

P9755 [CSP-S 2023] 种树 (二分+贪心)

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)\)

  1. 对于 \(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\)

  2. 对于 \(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;
}
posted @ 2024-03-24 10:59  Fire_Raku  阅读(265)  评论(0编辑  收藏  举报