CodeForces - 208E Blood Cousins(树上倍增+二分/树上启发式合并)

题目链接:点击查看

题目大意:给出n棵树,再给出m个询问,每次询问给出两个整数u和k,先假设u在k层之上的祖先是p,问与u在同一层深度,并且公共祖先都是p的节点有多少个

题目分析:因为先要求出u在第k层之上的祖先,我们可以利用树上倍增很简单的求出,但接下来的操作我是没想到的,虽然鲲学长已经给了提示需要二分,我也看出了直接dfs暴力找肯定会T掉的,但没设计出合适的二分来处理这个题,憋不住去网上看了题解后一下子就豁然开朗了,大概就是先用dfs序预处理一下,然后每一层开一个vector,用来储存每个节点在dfs序后的编号,因为dfs序的缘故,我们在求出u在第k层之上的祖先p后,就可以知道p的子树中的所有编号肯定都在[L[p],R[p]]之间了,那么我们只需要对于deep[u]这一层的向量进行二分,求出有多少个节点位于这个范围内就是答案了

实现简单但思路不好想,挺好的一个题目


2020. 1.14更新:

学了一波树上启发式合并,发现对于这个题目而言,要求以每个结点为根的子树的状态,不妨直接用启发式合并搞一波,因为给出的数据是针对于子节点和相对深度,我们可以利用树上倍增预处理出祖先节点和绝对深度,如此一来就是树上启发式合并的模板题了

代码:

二分:

#include<iostream>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<climits>
#include<cmath>
#include<cctype>
#include<stack>
#include<queue>
#include<list>
#include<vector>
#include<set>
#include<map>
#include<sstream>
using namespace std;
 
typedef long long LL;
 
const int inf=0x3f3f3f3f;
 
const int N=1e5+100;

vector<int>node[N];

vector<int>d[N],root;

int n,limit,max_deep;

int cnt=0;

int L[N],R[N];

int dp[N][20],deep[N];

void dfs(int u,int fa,int dep)//dfs序+树上倍增
{
	max_deep=max(max_deep,dep);
	L[u]=++cnt;
	d[dep].push_back(cnt);
	deep[u]=dep;
	dp[u][0]=fa;
	for(int i=1;i<=limit;i++)
		dp[u][i]=dp[dp[u][i-1]][i-1];
	for(int i=0;i<node[u].size();i++)
	{
		int v=node[u][i];
		if(v==fa)
			continue;
		dfs(v,u,dep+1);
	}
	R[u]=cnt;
}

int solve(int u,int p)
{
	int h=deep[u];
	for(int i=0;i<=limit;i++)//先跳到祖先p的位置
		if((p>>i)&1)
			u=dp[u][i];
	if(u==0)//若跳到0节点了,说明祖先不存在,直接返回0
		return 0;
	int a=L[u],b=R[u];//查找deep[u]层在[a,b]区间内有多少个节点
	int l=lower_bound(d[h].begin(),d[h].end(),a)-d[h].begin();
	int r=upper_bound(d[h].begin(),d[h].end(),b)-d[h].begin()-1;
	return r-l;
}

int main()
{
//  freopen("input.txt","r",stdin);
//	ios::sync_with_stdio(false);
	scanf("%d",&n);
	limit=log2(n)+1;
	for(int i=1;i<=n;i++)
	{
		int num;
		scanf("%d",&num);
		if(num)
			node[num].push_back(i);
		else
			root.push_back(i);
	}
	for(int i=0;i<root.size();i++)
		dfs(root[i],0,0);
	for(int i=0;i<=max_deep;i++)
		sort(d[i].begin(),d[i].end());
	int w;
	scanf("%d",&w);
	while(w--)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		printf("%d ",solve(a,b));
	}
	
	
	
	
	
	
	
	
	
     
     
     
     
     
     
    return 0;
}

树上启发式合并:

#include<iostream>
#include<cstdio> 
#include<string>
#include<ctime>
#include<cstring>
#include<algorithm>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<unordered_map>
using namespace std;

typedef long long LL;

const int inf=0x3f3f3f3f;

const int N=1e5+100;

int deep[N],son[N],num[N],dp[N][20],ans[N],cnt[N],limit;

bool vis[N];

vector<int>node[N];

vector<pair<int,int>>Q[N];//<id,deep>

void dfs_son(int u,int dep)
{
	son[u]=-1;
	num[u]=1;
	deep[u]=dep;
	for(int i=1;i<=limit;i++)
		dp[u][i]=dp[dp[u][i-1]][i-1];
	for(auto v:node[u])
	{
		dfs_son(v,dep+1);
		num[u]+=num[v];
		if(son[u]==-1||num[son[u]]<num[v])
			son[u]=v;
	}
}

void cal(int u,int val)
{
	cnt[deep[u]]+=val;
	for(auto v:node[u])
	{
		if(vis[v])
			continue;
		cal(v,val);
	}
}

void dfs(int u,int keep)
{
	for(auto v:node[u])
	{
		if(v==son[u])
			continue;
		dfs(v,0);
	}
	if(son[u]!=-1)
	{
		dfs(son[u],1);
		vis[son[u]]=true;
	}
	cal(u,1);
	for(auto i:Q[u])
	{
		int id=i.first;
		int d=i.second;
		ans[id]=cnt[d];
	}
	if(son[u]!=-1)
		vis[son[u]]=false;
	if(!keep)
		cal(u,-1);
}

int get_Ancestor(int x,int d)
{
	for(int i=0;i<=limit;i++)
		if((d>>i)&1)
			x=dp[x][i];
	return x;
}

int main()
{
//	freopen("input.txt","r",stdin);
//	ios::sync_with_stdio(false);
	int n,m;
	scanf("%d",&n);
	limit=log2(n)+1;
	for(int i=1;i<=n;i++)
	{
		int fa;
		scanf("%d",&fa);
		node[fa].push_back(i);
		dp[i][0]=fa;
	}
	dfs_son(0,0);
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		int u,d;
		scanf("%d%d",&u,&d);
		if(deep[u]<=d)
			ans[i]=1;
		else
		{
			int fa=get_Ancestor(u,d);
			Q[fa].push_back(make_pair(i,deep[u]));
		}
	}
	dfs(0,1);
	for(int i=1;i<=m;i++)
		printf("%d ",ans[i]-1);
	
	
	
	
	
	
	
	
	
	
	return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Frozen_Guardian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值