题目大意:在有根树上选择 k 个关键点,定义 f(x) 表示 x 到 root 的路径上,最近关键点的距离(若 x 是关键点,则距离为0)。对于 k ∈ [ 1 , n ] k \in[1,n] k∈[1,n],选择 k 个点时, f ( x ) f(x) f(x) 的最大值要尽可能小。输出对于所有的 k , f ( x ) f(x) f(x) 最大值尽可能小的和。
当 k 取某一个值时,有一个的二分 + 贪心做法:二分答案 x,每次从树上选择一个深度最深的叶子节点,将距离其 x 的祖先标记为关键点,并删除掉这个关键点的子树,通过这样贪心可以得到答案为 x 时,至少需要几个关键点。
注意到这个贪心的过程,当答案为 x 时,每选一个关键点,至少会删除 x + 1 x + 1 x+1 个节点,那么关键点的数量最多为 n x + 1 + 1 \displaystyle\frac{n}{x + 1} + 1 x+1n+1,因为根节点一定要选。
如果用线段树维护 区间 dfs 序的深度最大的节点,模拟这个贪心过程的复杂度为 O ( ( n x + 1 + 1 ) log n ) O((\displaystyle\frac{n}{x + 1} + 1)\log n) O((x+1n+1)logn)。
注意到如果枚举答案,对于答案 i i i 至少需要的关键点的数量是一个调和级数,而答案显然在 [ 0 , n ] [0,n] [0,n] 内,由于 0 不产生贡献,从 1 开始从小到大枚举答案 i i i,当前这个答案至少需要 c u r cur cur 个关键点,而答案 i − 1 i - 1 i−1 至少需要 l a s t c u r lastcur lastcur 个节点,那么可以计算答案 i i i 的贡献: ( l a s t c u r − c u r ) ∗ i (lastcur - cur) * i (lastcur−cur)∗i
不难发现这样做的复杂度为 O ( n log 2 n ) O(n\log^2 n) O(nlog2n),一个 l o g log log 来自线段树,一个 l o g log log 来自调和级数。
对于每个答案复原线段树,用栈记录每次修改操作带来的节点权值变化,逆序将节点权值还原即可。
代码:
#include
using namespace std;
const int maxn = 2e5 + 10;
#define pii pair
#define fir first
#define sec second
typedef long long ll;
vector<int> g[maxn];
vector<pair<int,pii> > op[maxn];
int n, deep[maxn], st[maxn], id[maxn], ed[maxn], cnt, tot;
int p[maxn][20];
struct seg_tree {
#define lson rt << 1, l, mid
#define rson rt << 1 | 1, mid + 1, r
int tag[maxn << 2], pos[maxn << 2];
void build(int rt,int l,int r) {
tag[rt] = 0, pos[rt] = 0;
if (l == r) {
pos[rt] = id[l];
return ;
}
int mid = l + r >> 1;
build(lson); build(rson);
if (deep[pos[rt << 1 | 1]] > deep[pos[rt << 1]])
pos[rt] = pos[rt << 1 | 1];
else
pos[rt] = pos[rt << 1];
}
void pushdown(int rt) {
if (!tag[rt]) return ;
tag[rt << 1] = tag[rt << 1 | 1] = tag[rt];
pos[rt << 1] = pos[rt << 1 | 1] = 0;
tag[rt] = 0;
}
void del(int L,int R,int rt,int l,int r) {
op[tot].push_back(make_pair(rt,pii(pos[rt],tag[rt])));
if (L > R || L == 0 || R == 0 || L > n || R > n) return ;
if (L <= l && r <= R) {
tag[rt] = 1;
pos[rt] = 0;
return ;
}
pushdown(rt);
int mid = l + r >> 1;
if (L <= mid) del(L,R,lson);
if (mid + 1 <= R) del(L,R,rson);
if (deep[pos[rt << 1 | 1]] > deep[pos[rt << 1]])
pos[rt] = pos[rt << 1 | 1];
else
pos[rt] = pos[rt << 1];
}
}tree;
void dfs1(int u) {
st[u] = ++cnt; id[cnt] = u;
for (auto it : g[u]) {
p[it][0] = u;
deep[it] = deep[u] + 1;
dfs1(it);
}
ed[u] = cnt;
}
void dfs2(int u) {
for (int i = 1; i <= 17; i++)
p[u][i] = p[p[u][i - 1]][i - 1];
for (auto it : g[u])
dfs2(it);
}
int getdis(int x,int y) {
int ans = x, r = 0;
while (y) {
if (y & 1) ans = p[ans][r];
r++;
y >>= 1;
}
return ans;
}
int get_ans(int x) { //答案为 x 时需要的关键点的个数
tot = 0;
while (tree.pos[1]) {
tot++;
int v = getdis(tree.pos[1],x);
if (v == 0) v = 1;
tree.del(st[v],ed[v],1,1,n);
}
for (int i = tot; i >= 1; i--) {
for (auto it : op[i]) {
tree.pos[it.first] = it.second.first;
tree.tag[it.first] = it.second.second;
}
op[i].clear();
}
return tot;
}
int main() {
deep[1] = 1; p[1][0] = 0;
while (~scanf("%d",&n)) {
cnt = 0; deep[1] = 1;
for (int i = 1; i <= n; i++)
g[i].clear();
for (int i = 2, x; i <= n; i++) {
scanf("%d",&x);
g[x].push_back(i);
}
dfs1(1); dfs2(1);
tree.build(1,1,n);
ll ans = 0;
int lst = n;
for (int i = 1; i <= n; i++) {
int cur = get_ans(i); //答案为 i至少要几个关键点
ans += 1ll * (lst - cur) * i;
lst = cur;
}
printf("%lld\n",ans);
}
return 0;
}