CSP-S R2 T4 种树题解
\(First\) -大意:
给定一颗以 \(1\) 为根有 \(n\) 个节点的树,可以在每个节点上种一颗高度为 \(0\) 的树,每天可以选择一个未种树且与某个已种树的节点通过一条边连接的节点种树,每个节点 \(i\) 的树种下后第 \(x\) 天( \(x\) 从整个任务的第一天开始计算)会成长 \(\max(b_i + c_ix,1)\) ,并且每个节点有一个高度要求 \(a_i\),特别的,第一天只能种在节点 \(1\) 上。问:需要最少多少天才能满足所有树的高度要求(即所有树的高度大于等于 \(a_i\) )?且数据保证存在方案可以在 \(10^9\) 天内完成任务。
\(Second\) -分析:
观察树的成长方式,可以发现对于每天成长的高度是一个关于 \(x\) 的一次函数( \(c<0\) 时是个分段函数),具体一点,如下:
设第 \(i\) 个节点的树第 \(x\) 天成长高度为 \(f_i(x)\),
当 \(c_i\ge 0\) 时: \(f_i(x)=c_ix+b_i\)。
当 \(c_i<0\) 时:
令 \(z=\left \lfloor\frac{b_i-1}{-c_i} \right \rfloor\),
则 \( f(x)= \begin{cases} &c_ix+b_i,x\le z \\ &1,x>z \end{cases}\)。
既然知道树每天生长的高度是个一次函数,那么就可以知道树种下后到第 \(y\) 天到底长了多高。以下推导当 \(c_i<0\) 时均认为 \(x\le z\)。
令 \(h_i\) 为节点 \(i\) 的树在第 \(x\) 天种下后到第 \(y\) 天的高度,则:
因为要求的高度为 \(a_i\),则要完成高度要求需要满足 \(h_i\ge a_i\),即 \(h_i-a_i\ge 0\),即可化为:
\(Third\) -定策
由上文可知,只要已知 \(x\) 或 \(y\) 中的一个,就可以转化为一个二次不等式问题,考虑如果某一个天所有结点的树都刚好满足要求,那么该天以后都满足要求,以前都不满足要求,具有单调性,且数据保证存在方案在 \(10^9\) 天以内可以完成任务,可以考虑对最终结束天二分,而确定了最终结束天也就可以确定每个节点的最晚种植天。求出每个节点的最晚种植天后可以考虑贪心,使用堆维护当前未种植节点中最晚种植天最早的节点,那么我们就从该节点向上一步一步走到第一个已种植的祖先,接着回溯到该节点,如果到这个节点的种植时间依旧晚于最晚种植天,则该节点无法被满足,也即该最终结束天是不合法的,如果所有节点都可以被满足,则该最终结束天合法。
现在考虑证明贪心正确性:因为当前节点是所有未种植节点中最晚种植天最早的,如果该节点没有办法被先满足,那么该方案不合法。如果该节点可以被满足,那么接下来在堆顶的节点就可以套用前论,如果所有节点都可以,那么结束天就是合法的,所以先满足堆顶节点可能不优,但绝对不劣。
\(Fourth\) -求解
接着考虑求出最晚种植天,有两种方法,一是二分法,二是直接解不等式法,本篇题解使用的是解不等式法,为方便书写和观看,以下公式已去除下标。
当 \(c=0\) 时,易得:\(x=y-\left \lfloor\frac{a-1}{b} \right \rfloor\)。
当 \(c\ne 0\) 时:
先将上文的不等式化为关于 \(x\) 的二次项系数为一的二次不等式,这里直接给出结果,过程请读者自行推导。
令 \(p=\frac{2b}{c}\),
若 \(c>0\) 时取大于等于,\(c<0\) 时反之,这里给出的是 \(c>0\) 的形式。
当 \(c>0\) 时,直接带入求根公式,\(\Delta\) 符号取正并对结果向下取整。
当 \(c<0\) 时,求出上文所写的 \(z\) ,即 \(\left \lfloor\frac{b_i-1}{-c_i} \right \rfloor\)。
-
若 \(z\ge y\),那么也直接带入求根公式,\(\Delta\) 符号取负并对结果向下取整。
-
若 \(y>z\) 分两类情况
-
若 \(y-z\ge a\) 则 \(x=y-a+1\)。
-
若 \(y-z<a\),则将上文的不等式中的 \(a\) 减少 \(y-z\),将 \(y\) 的值变为 \(z\),接着再带入求根公式,\(\Delta\) 符号取负并对结果向下取整。
-
\(Last\) -Code
时间复杂度为 \(O(n\log_2{n}\log_2{V})\),
其中 \(V\) 为最大天数也即题目所给的 \(10^9\)。
因为要使用 long double
所以要注意对精度的调整,我在洛谷上是选择的加上 \(0.01\) 进行微调,读者可以尝试使用别的方法。
格式化马蜂,不喜轻喷。
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define ll long long
#define ldb long double
#define ull unsigned long long
#define m_p make_pair
using namespace std;
using namespace __gnu_pbds;
inline ll read()
{
ll s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return s * w;
}
inline void print(ll ch)
{
if (ch < 0)
ch = -ch, putchar('-');
if (ch > 9)
print(ch / 10);
putchar(ch % 10 + '0');
}
const int N = 1e5 + 10;
ll c[N], b[N], ax[N], n, dep[N], tcnt, flg, ft[N], lat[N]; // last arrive time
vector<int> a[N];
bitset<N> vis;
void dfs(int x)
{
for (auto y : a[x])
{
if (y == ft[x])
continue;
ft[y] = x;
dfs(y);
}
}
void hfs(int x)
{
if (vis[x])
return;
hfs(ft[x]);
vis[x] = 1;
++tcnt;
if (lat[x] < tcnt)
flg = 1;
return;
}
ldb SE(ldb a1, ldb a2, ldb a3, ldb ty)
{
ldb delta = a2 * a2 - 4.0 * a1 * a3;
if (delta < 0)
return -1.0;
return (-a2 + ty * (sqrtl(delta))) / 2.0;
}
ldb calc(int pos, ldb y)
{
if (c[pos] == 0)
return y - (ax[pos] - 1ll) / b[pos];
else if (c[pos] > 0)
{
ldb c1 = 1.0 * c[pos];
ldb z = 2.0 * b[pos] / c1, z1 = 2.0 * ax[pos] / c1;
return SE(1.0, z - 1.0, -y * y - y - z * y - z + z1, 1.0);
}
else
{
ll dy = (b[pos] - 1ll) / (-c[pos]), la = ax[pos];
if (y - dy >= ax[pos])
return y - ax[pos] + 1ll;
if (y > dy)
{
la = la - y + dy;
y = dy;
}
ldb c1 = 1.0 * c[pos];
ldb z = 2.0 * b[pos] / c1, z1 = 2.0 * la / c1;
return SE(1.0, z - 1.0, -y * y - y - z * y - z + z1, -1.0);
}
}
bool check(ll dy)
{
tcnt = 0;
flg = 0;
vector<pair<ll, ll>> q;
for (int i = 1; i <= n; i++)
{
lat[i] = (calc(i, dy)+0.01);
if (lat[i] < 1)
return 0;
q.push_back(m_p(lat[i], i));
}
vis = 0;
ll x;
vis[0] = 1;
sort(q.begin(), q.end(), less<pair<ll, ll>>());
for (int i = 0; i < q.size(); i++)
{
x = q[i].second;
if (vis[x])
continue;
hfs(x);
if (flg)
return 0;
}
return 1;
}
int main()
{
ll ans = 0, x, y, z, V = 1e9 + 1;
n = read();
for (int i = 1; i <= n; i++)
{
ax[i] = read();
b[i] = read();
c[i] = read();
}
for (int i = 1; i < n; i++)
{
x = read();
y = read();
a[x].push_back(y);
a[y].push_back(x);
}
ll l = 0, r = V, mid;
mid = l + r >> 1;
dfs(1);
while (l <= r)
{
if (check(mid))
{
ans = mid;
r = mid - 1;
}
else
l = mid + 1;
mid = l + r >> 1;
}
print(ans);
return 0;
}
大部分推导在赛时已完成,除了正确的贪心算法(误。