LOJ 4168 「CCO 2024」Summer Driving
首先因为 A 不能走走过的路 B 可以走走过的路,于是 B 相当于是可以进行一些类似撤销的操作。
那么当 \(B\ge A\) 时,不管 A 走到哪了 B 都可以撤销至这个路上的任意一处,所以 B 一定可以使位置越来越接近最小值 \(1\)。
于是接下来就只需要考虑 \(A > B\) 的情况。
因为 B 除了 A 走不了后都不需要考虑走过的边,于是考虑从 B 入手为 A 作一些讨论。
于是假设 A 走到了点 \(u\),分析此时 B 能走到的点和走到的点的情况:
- B 可以走到 \(u\) 的 \(1\sim B\) 级祖先,且这些点到 \(u\) 的这个方向和到父亲的边的边都被封锁了。
- B 可以走到除 \(u\) 的 \(1\sim B\) 级祖先 \(u\) 的距离 \(\le B\) 的邻域的点,且这些点到父亲的边都被封锁了。
所以对于 A 来说,每个点一定是不能向父亲走的,且有可能有一条向儿子的边被封锁了(至多一条,这是因为 A 走下去后 B 走不回来了)。
于是能够发现实际对于双方来说的状态数并不多,于是可以直接设计状态:
- \(f_u\) 代表 A 在 \(u\) 这个点,没有向儿子的边被封锁。
- \(g_u\) 代表 B 在 \(u\) 这个点,可以走向任何一边。
- \(h_u\) 代表 A 在 \(\operatorname{fa}_u\) 这个点,且 \((\operatorname{fa}_u, u)\) 的这条边被封锁了。
对于转移也是考虑分讨:
- 对于 \(f_u, h_v(v\in \operatorname{son}_u)\):
记 \(S\) 为 \(u\) 去掉被封锁后的子树后能走到的距离为 \(A\) 的点,对 \(S\) 分讨:- 如果 \(|S| = 0\),也就是走不了,那么就是由 B 来操作最后一步,值就为除掉不能走的部分距离 \(\le B\) 邻域中的点权最小值。
- 如果 \(|S| > 0\),那么就是贪心的向最大的走,值为 \(\max_{s\in S} g_s\)。
- 对于 \(g_u\):
类似上文的分讨,记 \(S_1\) 为 \(u\) 的 \(0\sim B - 1\) 级祖先,\(S_2\) 为 \(u\) 的距离 \(\le B\) 的邻域去掉 \(u\) 的 \(1\sim B\) 级祖先,那么有转移 \(g_u = \min\{\min_{v\in S_1} h_v, \min_{v\in S_2} f_v\}\)。
于是现在就已经有了 \(\mathcal{O}(n^2)\) 的做法。
于是接下来考虑优化,一个很难受的事实是 \(f, g, h\) 的转移都与 \(\max, \min\) 有关。
于是考虑抛弃掉这个最值,对应的方法就是二分并把元素当作 \(0 / 1\),那么对于 \(\max\),就是存在 \(1\) 则 \(1\),无 \(1\) 则 \(0\);\(\min\) 也同理,存在 \(0\) 则 \(0\),无 \(0\) 则 \(1\)。
于是接下来就只需要关心是否有 \(0 / 1\) 了。
首先考虑 \(f_u, h_v(v\in \operatorname{son}_u)\) 的转移:
首先记 \(S\) 为 \(u\) 能往下走到的距离为 \(A\) 的点,那么再得知 \(S_v\) 表示 \(v\) 子树内 \(u\) 能往下走到的距离为 \(A\) 的点。
那么对于每个子树,首先需要关心子树内与 \(u\) 距离 \(\le B\) 的点是否有 \(0\),那么就可以贪心的维护子树内最浅的权值为 \(0\) 的点;其次还要关心是否存在 \(s\in S_v\) 满足 \(g_s = 1\),这时要注意到因为每个点至多一个 \(A\) 级祖先,所以 \(\sum\limits_{v = 1}^n |S_v|\le n\),直接暴力看就是对的。
于是整合各个子树及 \(u\) 自身信息,记录下最小及次小就可以在 \(\mathcal{O}(n)\) 的复杂度内得到 \(f_u, h_v\)。
接下来考虑 \(g_u\) 的转移:
同上文定义 \(S_1, S_2\)。
首先因为 \(g_u\) 涉及邻域的问题是可能向上走的,所以枚举顺序应当是处理出 \(\operatorname{dep}_u = d\) 的 \(f_u, h_v\) 再处理 \(\operatorname{dep}_u = d + B\) 的 \(g_u\)。
-
首先考虑 \(S_1\) 的转移。
还是同样的只考虑能到达的最近的 \(h_v\),那么可以用一个并查集,若 \(h_v = 0\) 指向自己,否则指向父亲,这样从 \(u\) 跳到的顶 \(t\) 就是 \(u\) 一直往上跳遇到的第一个 \(h_t = 0\),判断距离即可。 -
接下来考虑 \(S_2\) 的转移,考虑在扫到 \(s = \operatorname{LCA}(u, v)(f_v = 0)\) 时处理。
那么此时 \(v\) 就和 \(u\) 应该在 \(s\) 的不同子树中,且依然贪心的 \(\operatorname{dep}_v\) 应当尽量小,于是一样的可以考虑维护 \(s\) 的子树中的最小值及次小值就可以知道给其儿子 \(v\) 的子树的 \(\operatorname{dep}_{\min}\),那么就可以拆距离给 \([\operatorname{dfn}_v, \operatorname{rdfn}_v]\) 打上一个 \(\operatorname{dep}_{\min} - 2\operatorname{dep}_s\)。
然后在 \(u\) 的时候就可以询问 \(\operatorname{dfn_u}\) 这个位置的最小值 \(\operatorname{mn}\),然后判断 \(\operatorname{mn} + \operatorname{dep}_u\) 与 \(B\) 的关系。
需要注意的是,这样子是遗漏了 \(u\) 自己子树的信息的,于是同时还需要维护一下子树内 \(f_v = 0\) 的 \(v\) 的最浅深度并判断。
那么就在 \(\mathcal{O}(n\log n)\) 的复杂度下解决了 \(g_u\) 的转移。
加上二分的复杂度,最后总复杂度 \(\mathcal{O}(n\log^2 n)\)。
#include<bits/stdc++.h>
constexpr int inf = 1e9;
constexpr int maxn = 3e5 + 10;
int n, rt, A, B;
std::vector<int> son[maxn], to[maxn], du[maxn * 2];
int dep[maxn], fa[maxn], fid[maxn], dfn[maxn], rdfn[maxn], dn;
int stk[maxn];
inline void dfs1(int u) {
dep[u] = dep[fa[u]] + 1, dfn[u] = ++dn, stk[dep[u]] = u;
du[dep[u]].push_back(u);
if (fa[u]) son[u].erase(std::find(son[u].begin(), son[u].end(), fa[u]));
if (dep[u] - A >= 1) to[stk[dep[u] - A + 1]].push_back(u);
for (int i = 0; i < son[u].size(); i++) {
int v = son[u][i];
fa[v] = u, fid[v] = i;
dfs1(v);
}
rdfn[u] = dn;
}
int f[maxn], g[maxn], h[maxn];
int op[maxn], mnd[maxn], subd[maxn];
inline void dfs2(int u) {
mnd[u] = op[u] ? inf : dep[u];
for (int v : son[u]) {
dfs2(v);
mnd[u] = std::min(mnd[u], mnd[v]);
}
}
namespace DSU {
int fa[maxn];
inline void init() {
std::iota(fa + 1, fa + n + 1, 1);
}
inline int getfa(int x) {
return fa[x] == x ? x : (fa[x] = getfa(fa[x]));
}
}
namespace segtr {
int mn[maxn * 4];
inline void build(int k = 1, int l = 1, int r = n) {
mn[k] = inf;
if (l == r) return ;
int mid = l + r >> 1;
build(k << 1, l, mid), build(k << 1 | 1, mid + 1, r);
}
inline void update(int x, int y, int z, int k = 1, int l = 1, int r = n) {
if (x <= l && r <= y) return mn[k] = std::min(mn[k], z), void();
int mid = l + r >> 1;
if (x <= mid) update(x, y, z, k << 1, l, mid);
if (y > mid) update(x, y, z, k << 1 | 1, mid + 1, r);
}
inline int query(int x, int k = 1, int l = 1, int r = n) {
if (l == r) return mn[k];
int mid = l + r >> 1;
return std::min(mn[k], x <= mid ? query(x, k << 1, l, mid) : query(x, k << 1 | 1, mid + 1, r));
}
}
inline bool check(int K) {
for (int i = 1; i <= n; i++) op[i] = i > K;
dfs2(rt);
DSU::init(), segtr::build();
for (int d = n; d; d--) {
for (int u : du[d]) {
int id[2] = {-1, -1}, cnt[2] = {0, 0};
if (! op[u]) id[0] = -2;
for (int i = 0; i < son[u].size(); i++) {
int v = son[u][i];
if (to[v].size()) {
for (int vs : to[v]) cnt[g[vs]]++;
} else {
if (mnd[v] - dep[u] <= B) {
id[1] = id[0], id[0] = i;
}
}
}
for (int i = 0; i <= son[u].size(); i++) {
int v = i < son[u].size() ? son[u][i] : 0, &val = v ? h[v] : f[u];
for (int vs : to[v]) cnt[g[vs]]--;
if (cnt[1]) val = 1;
else if (cnt[0]) val = 0;
else val = id[0] == i ? (id[1] == -1 ? 1 : 0) : (id[0] == -1 ? 1 : 0);
for (int vs : to[v]) cnt[g[vs]]++;
}
for (int i = 0; i < son[u].size(); i++) {
int v = son[u][i];
if (h[v]) DSU::fa[v] = u;
}
int mn[2] = {inf, inf}, mni = -1;
for (int i = 0; i < son[u].size(); i++) {
int v = son[u][i];
if (subd[v] <= mn[0]) {
mn[1] = mn[0], mn[0] = subd[v], mni = i;
} else if (subd[v] <= mn[1]) {
mn[1] = subd[v];
}
}
for (int i = 0; i < son[u].size(); i++) {
int v = son[u][i], val = mni == i ? mn[1] : mn[0];
segtr::update(dfn[v], rdfn[v], val - 2 * dep[u]);
}
subd[u] = f[u]? inf : dep[u];
for (int v : son[u]) subd[u] = std::min(subd[u], subd[v]);
}
for (int u : du[d + B]) {
g[u] = 1;
int top = DSU::getfa(u);
if (top != rt && dep[u] - dep[top] <= B - 1) g[u] = 0;
if (dep[u] + segtr::query(dfn[u]) <= B) g[u] = 0;
if (subd[u] - dep[u] <= B) g[u] = 0;
}
}
return f[rt] == 0;
}
int main() {
scanf("%d%d%d%d", &n, &rt, &A, &B);
for (int i = 1, x, y; i < n; i++) {
scanf("%d%d", &x, &y);
son[x].push_back(y), son[y].push_back(x);
}
if (B >= A) return puts("1"), 0;
fa[rt] = 0, fid[rt] = -1, dfs1(rt);
int l = 1, r = n - 1, ans = n;
while (l <= r) {
int mid = l + r >> 1;
if (check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d\n", ans);
return 0;
}
浙公网安备 33010602011771号