E. Boomerang
传送门:https://codeforces.com/gym/105143/problem/E
题意
给定一个
n
n
n 个节点的树,根节点为
r
r
r , 设时刻
t
t
t 的点集
V
(
r
,
t
)
=
{
v
∣
d
i
s
(
r
,
v
)
≤
t
}
V(r, t) = \{v \; | \; dis(r, v) \leq t \}
V(r,t)={v∣dis(r,v)≤t},
其中
d
i
s
(
u
,
v
)
dis(u, v)
dis(u,v) 表示树上两点
u
u
u 和
v
v
v 间的唯一简单路径的边数。
再给定一个
t
0
t_0
t0,对每个
k
∈
[
1
,
n
]
k \in [1,n]
k∈[1,n],你需要选择一个点
r
0
r_0
r0,定义时刻
t
t
t 的点集
V
′
(
r
0
,
t
)
=
{
v
∣
d
i
s
(
r
0
,
v
)
≤
k
(
t
−
t
0
)
}
V ^\prime (r_0, t) = \{v \; | \; dis(r_0, v) \leq k(t−t_0) \}
V′(r0,t)={v∣dis(r0,v)≤k(t−t0)}。
找到一个最小的
t
t
t,使得
V
(
r
,
t
)
⊆
V
′
(
r
0
,
t
)
V(r, t) \subseteq V ^\prime (r_0, t)
V(r,t)⊆V′(r0,t)
即谣言在时刻
0
0
0 从点
r
r
r 开始传播,每个单位时间往外扩散一个点; 而辟谣在时刻
t
0
(
t
0
>
0
)
t_0 (t_0 > 0)
t0(t0>0) 从任意点
r
0
r_0
r0 开始传播,传播速度为
k
k
k。
现在需要对于每个
k
k
k,找到最早的辟谣完全覆盖谣言时间点(
r
0
r_0
r0 任选)。
思路
注意到,当谣言传播到时刻 t t t 时,这是一颗子树 V ( r , t ) V(r,t) V(r,t),且所有节点距离 r r r 的距离不超过 t t t,如果我们要在当前时刻覆盖所有的谣言的话,我们选择的那个开始辟谣的点 r r r,一定要最小化它到最远的点的距离,也就是说,我们 r r r 一定选在子树 V ( r , t ) V(r,t) V(r,t) 的直径的中点,此时距离最远的点最近,最大距离即为: ⌈ D 2 ⌉ \lceil \frac{D}{2} \rceil ⌈2D⌉, D D D 为子树 V ( r , t ) V(r,t) V(r,t) 直径
我们从 t 0 t_0 t0 开始辟谣,那么此时辟谣了 t − t 0 t - t_0 t−t0 秒,而我们要覆盖所有的谣言点,那么起码需要的辟谣速度 k = ⌈ ⌈ D 2 ⌉ t − t 0 ⌉ k =\lceil \dfrac{\lceil \dfrac{D}{2} \rceil }{t - t_0} \rceil k=⌈t−t0⌈2D⌉⌉。如果我们能从 t = 1 → t = t 0 + n t = 1 \rarr t = t_0 + n t=1→t=t0+n 枚举 t t t,获得当时的子树 V ( r , t ) V(r, t) V(r,t),并算出那时候覆盖子树所需的最小的辟谣速度 k k k,我们就能对这些 a n s k = m i n { t ∣ k = ⌈ ⌈ D t 2 ⌉ t − t 0 ⌉ } ans_k = min \{ t \mid k =\lceil \dfrac{\lceil \dfrac{D_t}{2} \rceil }{t - t_0} \rceil\} ansk=min{t∣k=⌈t−t0⌈2Dt⌉⌉},取 m i n min min 值
注意到子树扩散过程中,计算出来的 k k k 不一定覆盖 [ 1 , n ] [1,n] [1,n],其实随着 k k k 的增加, a n s [ k ] ans[k] ans[k] 是非递增的,道理很简单,如果在时刻 t t t 我们选取速度 k k k 可以覆盖这个时刻的子树的话,我们选取速度 k + 1 k + 1 k+1 只会更快覆盖这颗子树,所以我们只需要取 a n s k = m i n { a n s k , a n s k − 1 } ans_k = min \{ ans_k, ans_{k - 1} \} ansk=min{ansk,ansk−1}
现在问题在于:如果动态加点,维护树的直径?注意到我们随着 t t t 的增大,每次加入的点都是新扩散出去的,也就是当前子树的某些邻居点,类似于生成树。
因此我们有以下结论:
假设此时树的直径端点为 A , B A ,\; B A,B,直径为 D D D,新扩散加入一个点 u u u 时,新的直径只有三种可能:
- A − B , D A-B, \; D A−B,D
- A − u , d i s ( A , u ) A-u, \; dis(A,u) A−u,dis(A,u)
- B − u , d i s ( B , u ) B-u, \; dis(B,u) B−u,dis(B,u)
我们只需要用 L C A LCA LCA 计算两点的距离即可
时间复杂度: O ( n log n ) O(n \log n) O(nlogn)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;
const int INF=0x3f3f3f3f;
const long long INFLL=1e18;
typedef long long ll;
const int N = 200050;
std::vector<int> g[N];
int fa[N][20];
int dep[N];
void dfs(int u, int father){
dep[u] = dep[father] + 1;
fa[u][0] = father;
for(int i = 1; (1 << i) <= dep[u]; ++i)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for(auto v : g[u])
if(v ^ father)
dfs(v, u);
}
int lca(int u, int v){
if(dep[u] < dep[v]) std::swap(u, v);
for(int i = 19; i >= 0; --i)
if(dep[u] - (1 << i) >= dep[v])
u = fa[u][i];
if(u == v) return v;
for(int i = 19; i >= 0; --i)
if(fa[u][i] != fa[v][i]){
u = fa[u][i];
v = fa[v][i];
}
return fa[u][0];
}
int dis(int u, int v){
int L = lca(u, v);
return dep[u] + dep[v] - 2 * dep[L];
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int n;
std::cin >> n;
fore(i, 1, n){
int u, v;
std::cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
int r, t0;
std::cin >> r >> t0;
dfs(1, 0);
std::vector<int> ans(n + 1, t0 + n);
int A = r, B = r; //直径的两个端点
int D = 0; //直径
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>,
std::greater<std::pair<int, int>>> que;
fore(i, 1, n + 1){
if(i == r) continue;
int d = dis(i, r);
if(d <= t0){
int d1 = dis(A, i), d2 = dis(B, i);
if(D >= std::max(d1, d2)) continue;
if(d1 > d2){
D = d1;
B = i;
}
else{
D = d2;
A = i;
}
}
else que.push({d, i});
}
fore(t, t0 + 1, t0 + n + 1){
while(!que.empty() && que.top().fi == t){
auto [d, i] = que.top();
que.pop();
int d1 = dis(A, i), d2 = dis(B, i);
// Debug(i, endl)
if(D >= std::max(d1, d2)) continue;
/* 更新直径 */
if(d1 > d2){
D = d1;
B = i;
}
else{
D = d2;
A = i;
}
}
int k = ((D + 1) / 2 + t - t0 - 1) / (t - t0);
ans[k] = std::min(ans[k], t);
}
fore(i, 1, n + 1){
ans[i] = std::min(ans[i], ans[i - 1]);
std::cout << ans[i] << ' ';
}
return 0;
}