权值线段树模板+练习题整理(刷题中)

权值线段树就是把线段树的每个点权,赋予一定的含义,比如数字出现的次数,数值前缀出现的次数,并用区间求和维护一个前缀信息,比如数字出现的次数,第K大等(不能实现区间第K大),前缀第K大等。

权值线段树本质是线段树维护桶,桶是一种数据结构。数据结构的用途是以一种特殊方式统计数据,使得我们能够快速地修改、查询我们想要的那部分数据。但是一般我们在想要统计一组数据的时候,我们更关注的是这些数据都是什么。就比如我们现在要统计一个数列,我们更关心的是这个数列里到底有那些数,而不是特别关心这些数都出现了几次。

桶就打破了这个现状,作为一种“特殊”的数据结构,它所统计的就是每个数据在数据集合中一共出现了多少次

在实现上,由于值域范围通常较大,权值线段树会采用离散化或动态开点的策略优化空间。

主要是参考下面这个博客:权值线段树总结


P3369 【模板】普通平衡树(权值线段树模板题)

1、离线处理+离散化
2、因为只有单点修改,所以可以省略pushup函数
3、前驱、后继的求法
4、按照每个数出现的次数建树
5、还有其他解法:splay、treap、树状数组+二分,等等

#define ls		i<<1
#define rs		i<<1|1
#define mid		((l+r)>>1)
#define lson	ls,l,mid
#define rson	rs,mid+1,r
const int maxn=1e5+7;
int n,m,sum[maxn<<2],a[maxn],b[maxn],c[maxn],cnt;
void add(int x,int k,int i=1,int l=0,int r=cnt-1){
	sum[i]+=k;
	if(l==r)	return;
	if(x<=mid)	add(x,k,lson);
	else		add(x,k,rson);
}
int kth(int x,int i=1,int l=0,int r=cnt-1){
	if(l==r)	return l;
	if(sum[ls]>=x)	return kth(x,lson);
	return kth(x-sum[ls],rson);
}
int query(int x,int i=1,int l=0,int r=cnt-1){
	if(l==r)	return 1;
	if(x<=mid)	return query(x,lson);
	return sum[ls]+query(x,rson);
}
int main(){
	n=read();
	for(int i=0;i<n;i++){
		c[i]=read();	a[i]=read();
		if(c[i]!=2&&c[i]!=4)	b[cnt++]=a[i];
	}
	sort(b,b+cnt);	cnt=unique(b,b+cnt)-b;
	for(int i=0;i<n;i++)	if(c[i]!=4)	a[i]=lower_bound(b,b+cnt,a[i])-b;
	for(int i=0;i<n;i++){
		if(c[i]==1)	add(a[i],1);
		if(c[i]==2)	add(a[i],-1);
		if(c[i]==3)	printf("%d\n",query(a[i]));
		if(c[i]==4)	printf("%d\n",b[kth(a[i])]);
		if(c[i]==5)	printf("%d\n",b[kth(query(a[i])-1)]);
		if(c[i]==6)	printf("%d\n",b[kth(query(a[i]+1))]);
	}
}

HDU 1394

题目大意:给定一个(每个数的范围在[1,n])数组,可以将反复将数组第一个数移到最后一位。求其中的最小逆序数。

1、逆序数的树状数组模板(就是权值线段树的一种)
2、把a[0]移到最后后,减少逆序数a[0],同时增加逆序数n-a[0]-1个。值变化:ans-a[0]+n-a[0]-1;

const int maxn=5e3+5;
int n,a[maxn],t[maxn],sum;
int lowbit(int x){	return x&(-x);	}
void add(int i,int x){
	while(i<=n){
		t[i]+=x;
		i+=lowbit(i);
	}
}
int query(int i){
	int ans=0;
	while(i>0){
		ans+=t[i];
		i-=lowbit(i);
	}
	return ans;
}
int main(){
	while(cin>>n){
		memset(t,0,sizeof(t));
		int ans=0;
		for(int i=1;i<=n;i++){
			a[i]=read();	a[i]++;
			ans+=query(n)-query(a[i]);
			add(a[i],1);
		}
		sum=ans;
		for(int i=1;i<=n;i++){
			ans+=n-a[i]-(a[i]-1);
			sum=min(sum,ans);
		}
		printf("%d\n",sum);
	}
}


待更新

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值