Ancient Distance
题目描述:
作为
的成员,
是一个具有出色数据结构技能的男孩。
请考虑以下问题:给出具有
个顶点的根树。 顶点的编号从
到
,并且根始终是顶点
。 您最多可以分配
个关键顶点,以使所有顶点之间的最大“祖先距离”尽可能小。 将顶点
的“祖先距离”表示为:
与从
到根的路径上的第一个关键顶点之间的距离。 例如,如果树为
,关键顶点集为
2
,则所有顶点的“祖先距离”为
+
,
,
然后加强了这个问题:请找到每个
,
,
,
的答案。 您可以接受
的挑战吗?
输入描述:
输入包含多个测试用例。
对于每个测试用例,第一行包含一个整数
),表示树中的顶点数。第二行中有
个整数,
整数
表示
与
之间存在一条边。它保证最多有
个测试用例,并且在所有测试用例中,
之和不会超过
输出描述:
对于每个测试用例,输出 分别为 , ,…, 时答案的和
样例输入:
3
1 2
3
1 1
样例输出:
3
2
思路:
二分答案
线段树维护
这道题一看到求最大值最小就想到二分
每次贪心查找最远的点,向上k个点放关键点,然后删左子树(注:“删除”指打标记,因为之后的计算原树还要用)直到所有的点都被删除时改变二分上下界,最后得出答案。
做法:
按顺序枚举
~
,由于需要寻找最大值,所以用线段树维护整棵树的dfs序。
枚举k
每次查找
调和级数
二分
总时间复杂度:
AC Code
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e6+5;
int a[MAXN],dep[MAXN],d[MAXN],ans[MAXN],v[MAXN];
int n,cnt,maxn,maxm,mid;
vector<int> vec[MAXN];
void dfs(int x)
{
d[++cnt]=x;
for(int i=0;i<vec[x].size();i++)
{
dep[vec[x][i]]=dep[x]+1;
dfs(vec[x][i]);
}
}//dfs序
void build(int left,int right,int l,int r)
{
if(left>right||l>r) return;
if(l==r)
{
for(int i=left;i<=right;i++)
ans[i]=l;
return;
}
mid=(left+right)/2;
ans[mid]=0;
for(int i=1;i<=n;i++)
v[i]=dep[i];
for(int i=n;i>=1;i--)
{
if(v[d[i]]==dep[d[i]]+mid||d[i]==1)
{
ans[mid]++;
v[d[i]]=-1;
}
v[a[d[i]]]=max(v[a[d[i]]],v[d[i]]);
}
build(left,mid-1,ans[mid],r);
build(mid+1,right,l,ans[mid]);
}//二分+线段树
int main()
{
while(~scanf("%d",&n))
{
memset(ans,0,sizeof(ans));
memset(dep,0,sizeof(dep));
dep[1]=cnt=maxn=maxm=0;
for(int i=1;i<=n;i++)
vec[i].clear();
for(int i=2;i<=n;i++)
{
scanf("%d",a+i);
vec[a[i]].push_back(i);
}
dfs(1);
for(int i=1;i<=n;i++)
maxn=max(maxn,dep[i]);
build(0,maxn,1,n);
for(int i=1;i<=maxn;i++)
maxm+=i*(ans[i-1]-ans[i]);
printf("%d\n",maxm);
}
}