2020牛客暑期多校训练营(第四场) A Ancient Distance
题解:这里主要讲一下官方题解的调和级数是怎么来的,因为当我们确定了一个最大 \(Ancient Distance = x\) 时,我们可以最多只用 \(\frac{n}{x}\) 个关键的就可以让这棵树的 \(Ancient Distance = x\) ,所以我们用 \(ans[i]\) 表示当关键点个数为 \(i\) 时,\(Ancient Distance\) 的最小值, 然后从 \(n - 1\) 到 \(1\) 枚举 \(x\) ,每次只要找不大于 \(\frac{n}{x}\) 个点就可以确定当 \(Ancient Distance = x\) 时所需要的最小关键点,所以我们不考虑寻找深度的最小的点时的复杂度为 \(\sum_{i = 1}^{n}\frac{n}{i} = n\log n\) ,由于我们时反向遍历的,所以最后的 \(ans[i]\) 一定会是最小值,关于找最小点可以线段树维护 \(dfs\) 序,每次查询的复杂度为 \(\log n\),所以总的复杂度为 \(n\log n \log n\)
第一份是官方题解的做法:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long LL;
#define lc (rt << 1)
#define rc ((rt << 1) | 1)
const int maxn = 2e5 + 50;
const LL mod = 1e9 + 7;
double eps = 1e-6;
int n;
struct Edge
{
int to, next;
} edge[maxn * 2];
int k, head[maxn];
void add(int a, int b){
edge[k].to = b;
edge[k].next = head[a];
head[a] = k++;
}
int dfn[maxn], dep[maxn], ln[maxn], rn[maxn], tot, fa[maxn][30], nodeid[maxn];
void dfs(int u, int pre, int d){
fa[u][0] = pre;
ln[u] = ++tot;
nodeid[tot] = u;
dep[tot] = d;
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(to == pre) continue;
dfs(to, u, d + 1);
}
rn[u] = tot;
}
void init(){
dfs(1, -1, 0);
for(int j = 0; (1 << (j + 1)) < n; j++){
for(int i = 1; i <= n; i++){
if(fa[i][j] < 0) fa[i][j + 1] = -1;
else fa[i][j + 1] = fa[fa[i][j]][j];
}
}
}
int LCA(int u, int len){ // 倍增找向上走 len 步的节点
len = min(len, dep[ln[u]]);
for(int i = 0; (1 << i) <= len; i++){
if(len & (1 << i)) u = fa[u][i];
}
return u;
}
int getMax(int u, int v){
if(dep[u] >= dep[v]) return u;
else return v;
}
int Lazy[maxn << 2];
int tree[maxn << 2];
void PushUp(int rt){
tree[rt] = 0;
if(!Lazy[lc]) tree[rt] = getMax(tree[rt], tree[lc]);
if(!Lazy[rc]) tree[rt] = getMax(tree[rt], tree[rc]);
}
void Build(int le, int ri, int rt){
if(le == ri){
tree[rt] = le;
return ;
}
int mid = (le + ri) >> 1;
Build(le, mid, lc);
Build(mid + 1, ri, rc);
PushUp(rt);
}
void Update(int le, int ri, int L, int R, int val, int rt){
if(L <= le && ri <= R){
Lazy[rt] = val;
return ;
}
int mid = (le + ri) >> 1;
if(L <= mid) Update(le, mid, L, R, val, lc);
if(R > mid) Update(mid + 1, ri, L, R, val, rc);
PushUp(rt);
}
vector<int> vec;
int ans[maxn];
int main()
{
while(~scanf("%d", &n)){
k = tot = 0;
for(int i = 1; i <= n; i++) head[i] = -1;
for(int i = 1; i < n; i++){
int x;
scanf("%d", &x);
add(x, i + 1);
add(i + 1, x);
}
init();
for(int i = 0; i <= n; i++){
ans[i] = n - 1;
}
Build(1, n, 1);
for(int i = n - 1; i >= 0; i--){
int cost = 1;
while(1){
int u = tree[1];
if(dep[u] <= i) break;
u = LCA(nodeid[u], i);
vec.push_back(u);
cost++;
Update(1, n, ln[u], rn[u], 1, 1);
}
int len = vec.size();
for(int u = 0; u < len; u++) Update(1, n, ln[vec[u]], rn[vec[u]], 0, 1);
vec.clear();
ans[cost] = i;
}
LL res = ans[1];
for(int i = 2; i <= n; i++){
ans[i] = min(ans[i - 1], ans[i]);
res += ans[i];
}
printf("%lld\n", res);
}
return 0;
}