洛谷3605 [USACO17JAN]Promotion Counting晋升者计数 线段树合并 离散化

题目链接

题意:
给你一个n个点的树,每个点有一个点权,问你每个点的子树里有多少个点的权值比当前点的权值小。n<=100000

题解:
我搜出了是个线段树合并的题,知道该用什么算法之后还是比较好做的了。

首先线段树肯定是要建动态开点的权值线段树,由于权值范围很大,所以肯定要先离散化一下。

离散化之后,根据建出的树dfs一遍,从下向上合并。我的写法是,先把每个子树的线段树的信息都合并到当前点的线段树上,然后再在当前点的线段树上查询比当前点权值大的点的个数,然后再把当前点的权值加进线段树。

看来我现在的码力也就是能自己想出和写出这种水题了,感觉同一机房的大佬早就不屑于写这种水题了啊QAQ。

代码:

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

int n,val[100010],hed[200010],cnt,num,b[100010],fa[100010],ans[100010],root[100010];
struct node
{
	int to,next;
}a[200010];
struct tree
{
	int l,r,s;
}tr[4000010];
inline int read()
{
	int x=0;
	char s=getchar();
	while(s>'9'||s<'0')
	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 update(int &rt,int l,int r,int x)
{
	if(!rt)
	rt=++cnt;	
	tr[rt].s++;
	if(l==r)
	return;
	int mid=(l+r)>>1;
	if(x<=mid)
	update(tr[rt].l,l,mid,x);
	else
	update(tr[rt].r,mid+1,r,x);
}
inline void merge(int &l,int r)
{
	if(!l||!r)
	{
		l=l+r;
		return;
	}
	tr[l].s+=tr[r].s;
	merge(tr[l].l,tr[r].l);
	merge(tr[l].r,tr[r].r);
}
inline int query(int rt,int l,int r,int x)
{
	if(l==r)
	return 0;
	int res=0,mid=(l+r)>>1;
	if(x<=mid)
	res=tr[tr[rt].r].s+query(tr[rt].l,l,mid,x);
	else
	res=query(tr[rt].r,mid+1,r,x);
	return res;
}
inline void dfs(int x)
{
	for(int i=hed[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(y==fa[x])
		continue;
		fa[y]=x;
		dfs(y);
		merge(root[x],root[y]);
	}
	ans[x]=query(root[x],1,num,val[x]);
	update(root[x],1,num,val[x]);
}
int main()
{
	n=read();
	for(int i=1;i<=n;++i)
	{
		val[i]=read();
		b[i]=val[i];
	}
	sort(b+1,b+n+1);
	num=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=n;++i)
	val[i]=lower_bound(b+1,b+num+1,val[i])-b;
	for(int i=2;i<=n;++i)
	{
		int x=read();
		add(i,x);
		add(x,i);
	}
	cnt=0;
	dfs(1);
	for(int i=1;i<=n;++i)
	printf("%d\n",ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值