牛客多校4 - Ancient Distance(树上倍增+dfs序+线段树)

题目链接:点击查看

题目大意:给出一棵 n 个节点且以点 1 为根节点的的树,现在给出一个 k ,需要在树上选择 k 个关键点,使得 n 个点到达根节点的路径上,出现的最近的关键点的距离的最大值最小,现在需要输出 k 分别为 1 ~ n 时的答案之和

题目分析:首先理解题意,这个题目中有且仅由一个思维点需要转换过来,那就是直接计算 k 值下的答案是很难计算的,但是我们可以在某个答案下,贪心去计算 k 的值

怎么理解上面这段话呢?对于某个答案 x 来说,因为 x 是题目中所有距离最大值的最小值,换句话说,任何一个点到达根节点的路径上,相离最近的那个关键点的距离一定小于等于 x 的,这样的话我们可以贪心去模拟,假设 x 已经确定了,可以先找到当前深度最深的那个节点,显然如果想要让这个节点距离最近的关键点的距离小于等于 x 的话,那么令其向上的第 x 个祖先设置为关键点就好了,当刚才的那个祖先被设置为关键点后,其子树上的所有点也就都满足条件了(因为最深的那个点都满足条件了,那么其余的点显然也是满足条件的),依次类推,贪心去模拟就能计算出 k 的值了

现在我们需要选择合适的数据结构去实现上述操作,对于上段所述的功能一一对应一下,子树对应着dfs序,有了dfs序就可以建立线段树,这样每次找深度最深的结点就可以用线段树维护dfs序上深度的最大值,找向上第 x 个祖先,对应着树上倍增,然后就是枚举答案了,显然答案的可行范围是 0 ~ n - 1 (当所有点都是关键点时,答案为 0 ,当只有根节点是关键点,且整棵树退化为链时,那么答案就是 n - 1 ),对于每次枚举的答案为 i ,贪心需要模拟的次数就为 n / i ,整体的时间复杂度就是 \sum_{i=1}^{n} \frac{n}{i}=nlogn(调和级数),再加上线段树更新需要的一层log,总的时间复杂度为 nlognlogn

注意一下,因为可能会出现不同的答案对应着相同的 k 值,所以我们可以倒着枚举答案,最后再正着维护一下答案的最小值就好了,因为根据贪心的策略,ans[ i ] 代表的就是当 k = i 时的答案,显然在 ans[ i ] 的策略上再增加关键点的话,ans[ i + 1 ] 一定是小于等于 ans[ i ] 的,所以从前往后维护最小值是可行的

最后就是对于线段树的更新,对于每个样例我们只需要 build 一次线段树即可,对于每个答案在更新线段树的时候,记录一下更新的位置,计算完答案后再原路恢复就好了,如果对于每个答案都重新 build 的话,时间复杂度就会退化成 n * n * logn * logn

代码:
 

#pragma comment(linker,"/STACK:102400000,102400000")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;
 
typedef long long LL;
 
typedef unsigned long long ull;
 
const int inf=0x3f3f3f3f;

const int N=2e5+100;

int dp[N][25],ans[N],L[N],R[N],id[N],deep[N],cnt,limit,n;

vector<int>node[N];

void dfs(int u,int dep)
{
	deep[u]=dep;
	L[u]=++cnt;
	id[cnt]=u;
	for(int i=1;i<=limit;i++)
		dp[u][i]=dp[dp[u][i-1]][i-1];
	for(auto v:node[u])
		dfs(v,dep+1);
	R[u]=cnt;
}

struct Node
{
	int id,deep;
	bool operator<(const Node& t)const
	{
		if(deep!=t.deep)
			return deep<t.deep;
		return id<t.id;
	}
}tree[N<<2],temp[N<<2];

int tot=0;

void build(int k,int l,int r)
{
	if(l==r)
	{
		tree[k].deep=deep[id[l]];
		tree[k].id=id[l];
		temp[k]=tree[k];
		return;
	}
	int mid=l+r>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	temp[k]=tree[k]=max(tree[k<<1],tree[k<<1|1]);
}

void update(int k,int l,int r,int L,int R,int op)//l,r:目标区间,L,R:当前区间 
{
	if(R<l||L>r)
		return;
	if(L>=l&&R<=r)
	{
		if(op==1)
			tree[k].deep=-1;
		else
			tree[k].deep=temp[k].deep;
		return;
	}
	int mid=L+R>>1;
	update(k<<1,l,r,L,mid,op);
	update(k<<1|1,l,r,mid+1,R,op);
	tree[k]=max(tree[k<<1],tree[k<<1|1]);
}

int get_fa(int u,int x)
{
	for(int i=0;i<=limit;i++)
		if(x&(1<<i))
			u=dp[u][i];
	return max(u,1);
}

int cal(int x)
{
	int num=0;
	vector<int>cls;//恢复操作用 
	while(tree[1].deep!=-1)
	{
		num++;
		int pos=get_fa(tree[1].id,x);
		cls.push_back(pos);
		update(1,L[pos],R[pos],1,n,1);
	}
	for(auto v:cls)
		update(1,L[v],R[v],1,n,-1);
	return num;
}

void init(int n)
{
	limit=log2(n)+1;
	cnt=0;
	for(int i=0;i<=n;i++)
	{
		ans[i]=inf;
		node[i].clear();
	}
}

int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.in.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false);
	while(scanf("%d",&n)!=EOF)
	{
		init(n);
		for(int i=2;i<=n;i++)
		{
			scanf("%d",&dp[i][0]);
			node[dp[i][0]].push_back(i);
		}
		dfs(1,0);
		build(1,1,n);
		for(int i=n;i>=0;i--)
		{
			int q=cal(i);
			ans[q]=i;
		}
		for(int i=1;i<=n;i++)
			ans[i]=min(ans[i],ans[i-1]);
		LL ans=0;
		for(int i=1;i<=n;i++)
			ans+=::ans[i];
		printf("%lld\n",ans);
	}






















    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_45458915/article/details/107588418
今日推荐