洛谷3899 谈笑风生 线段树合并

题目链接

题意:
给你一棵n个点的树,边的边权都是1。有q次询问,每次询问给一个a一个x,表示询问满足下列条件的三元组的个数:(a,b,c),使得a和b都是c的祖先节点并且a与b的距离不超过x。n,q<=300000

题解:
乍一看确实不太好做,如果你没有想到正确的算法的话可能不好做。这个题的做法好像很多,我在这里只介绍一种用线段树合并做的在线做法。

我们考虑告诉你了a之后,b和c会是怎么组成的。我们会发现,因为要a和b都是c的祖先节点,所以a,b,c应该在一条链上。我们分两种情况,一种是b是a的祖先,一种是b是a的儿子。对于第一种情况,我们发现对答案是b可以是a的父节点的任意一个,c可以在a的子树中任选一个,那么我们处理出深度和子树大小,那么对答案的贡献是 ( s i z e − 1 ) ∗ m i n ( x , d e p [ x ] ) (size-1)*min(x,dep[x]) (size1)min(x,dep[x])。我们考虑b在a的子树中的情况,那么对于每一个b,我们可以再在b的子树中任选一个点,那么对于每一个b,它的贡献都是它的子树大小减1(减去自己)。但是我们还要记录每一个贡献是在哪一个深度范围才能统计进答案的,所以我们对于每一个节点开一个动态开点的权值线段树来维护答案。具体来讲就是我们把深度看作权值,在当前线段树对应的深度处加上子树大小减一。我们用一个权值我们在回答询问前先预处理一下,就是一边dfs一边线段树合并。

线段树合并的时候注意一下写法,继承的时候如果之前直接继承了一个子节点的线段树,那么在之后合并的时候,两棵线段树都有这个子树的话,我们要新建节点来让信息相加,以免让子节点线段树的答案被改掉而丢失。当然这些都是细节问题。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,q,hed[300010],cnt,fa[300010],dep[300010],num,root[300010],sz[300010],ji;
struct node
{
	int to,next;
}a[600010];
struct tree
{
	int l,r;
	long long s;
}tr[22000000];
inline int read()
{
	int x=0;
	char s=getchar();
	while(s<'0'||s>'9')
	s=getchar();
	while(s>='0'&&s<='9')
	{
		x=x*10+s-'0';
		s=getchar();
	}
	return x;
}
inline void add(int from,int to)
{
	a[++cnt].to=to;
	a[cnt].next=hed[from];
	hed[from]=cnt;
}
inline void merge(int &x,int l,int r)
{
	if(!l||!r)
	{
		x=l+r;
		return;
	}
	x=++num;
	tr[x].s=tr[l].s+tr[r].s;
	merge(tr[x].l,tr[l].l,tr[r].l);
	merge(tr[x].r,tr[l].r,tr[r].r);	
}
inline void update(int &rt,int l,int r,int x,int y)
{
	if(!rt)
	rt=++num;
	tr[rt].s+=y;
	if(l==r)
	return;
	int mid=(l+r)>>1;
	if(x<=mid)
	update(tr[rt].l,l,mid,x,y);
	else
	update(tr[rt].r,mid+1,r,x,y);
}
inline void dfs(int x)
{
	sz[x]=1;
	for(int i=hed[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(y==fa[x])
		continue;
		fa[y]=x;
		dep[y]=dep[x]+1;
		dfs(y);
		sz[x]+=sz[y];
	}
	update(root[x],1,n,dep[x],sz[x]-1);
	if(fa[x])
	{
		ji=0;
		merge(ji,root[fa[x]],root[x]);
		root[fa[x]]=ji;
	}
}
inline long long query(int rt,int l,int r,int le,int ri)
{
	if(le<=l&&r<=ri)
	return tr[rt].s;	
	int mid=(l+r)>>1;
	long long res=0;
	if(le<=mid)
	res+=query(tr[rt].l,l,mid,le,ri);
	if(mid+1<=ri)
	res+=query(tr[rt].r,mid+1,r,le,ri);
	return res;
}
int main()
{
	n=read();
	q=read();
	for(int i=1;i<=n-1;++i)
	{
		int x=read(),y=read();
		add(x,y);
		add(y,x);
	}
	dep[1]=1;
	dfs(1);
	for(int i=1;i<=q;++i)
	{
		int x=read(),y=read();
		long long ans;
		ans=query(root[x],1,n,dep[x]+1,dep[x]+y)+1ll*(long long)min(dep[x]-1,y)*(sz[x]-1);
		printf("%lld\n",ans);
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值