【权值线段树】离散化介绍 (+利用 线段树 求逆序对)

先介绍一下离散化
桶排大家应该知道,就是开一个数组(下标为数值,记录了该数值的出现次数)然后遍历过去如果出现次数不为零,那就输出这些数字,理论时间复杂度可以达到O(N)但是由于内存限制,不能开很大的数组。

然而 如果某个数列中的数字不要求大小确定,只要求这些数字有相对的大小就够了的话,离散化就有了用武之地

举个例子:数列

3 8 7 5 2000000000000000

我们发现有几个数之间差距很大,但是我们用不到数值的大小,只要求相对大小,那怎么办呢?
观察下面的数列:

1 4 3 2 5

真巧,这个数列和上面的数列各个数字之间的相对大小是一样的并且,让很大的数据缩小了,这样离散化了之后就可以处理一些原本处理不了的问题

离散化比较OK的复杂度是O(NlogN)
STL大法好,代码见下:

	for(int i=1;i<=n;++i)
		a[i]=read(),b[i]=a[i];//读入数组a,b数组记录下,等会儿用(read是读入优化)
	sort(b+1,b+n+1);//排序b数组,避免a数组顺序打乱
	int len=unique(b+1,b+n+1)-b-1;//unique:STL自带函数,去重并返回去重后数组的长度+1(所以这里后面要-1)
	//(原理上来讲不是这样,但这样理解方便)
	for(int i=1;i<=n;++i){
		int pos=lower_bound(b+1,b+n+1,a[i])-b;//STL自带函数,返回b(有序数组)中第一个大于等于a[i]的位置
		a[i]=pos;//改变这个值为找到的位置
	} 

介绍一种神奇的算法:

权值线段树

顾名思义,该线段树中存储的并不是普通线段树记录的线段端点,而是 类似一个桶一样的东西

最简单的例子:求逆序对
该题可以用树状数组做,可以用归并排序思想做,可以用splay做…
不过,既然可以用树状数组用,也可以用线段树做
####【思路】

先建一棵很大的线段树,然后用权值为下标建树,每一次读到一个数,就找比这个数大的个数,找到了之后加到ans里,接着更新线段树(这个数出现的次数+1)
PS:这题要加离散化,否则……你懂的

#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#define lson i << 1
#define rson i << 1 | 1
#define M 40005
using namespace std;

inline int read(){
	char chr=getchar();
	int f=1,ans=0;
	while(!isdigit(chr)) {if(chr=='-') f=-1;chr=getchar();}
	while(isdigit(chr))  {ans=ans*10;ans+=chr-'0';chr=getchar();}
	return ans*f;

}
int n;
struct Node{
	int l,r,v;
}t[M<<2];
int a[M],b[M];
void kai(){
	freopen("test1.txt","r",stdin);
}

void push_up(int i){
	t[i].v=t[lson].v+t[rson].v;
}//向上更新

void build(int i,int l,int r){//建树
	t[i].l=l;	t[i].r=r;	t[i].v=0;
	if(l==r){
		return;
	}
	int mid=t[i].l+t[i].r>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
}

void updata(int i,int x){
	if(t[i].l==t[i].r){
		++t[i].v;//这个数字出现的次数+1
		return;
	}
	int mid=t[i].l+t[i].r>>1;
	if(x<=mid) updata(lson,x);
	else       updata(rson,x);
	push_up(i);
}//更新节点

int query(int i,int l,int r){
	if(l<=t[i].l&&t[i].r<=r) return t[i].v;
	int mid=t[i].l+t[i].r>>1;
	int x=0;
	if(l<=mid) x+=query(lson,l,r);
	if(mid<r)  x+=query(rson,l,r);
	return x;
}//询问

int main(){
//	kai();
	n=read();
	int ans=0;
	build(1,1,M);
	for(int i=1;i<=n;++i)
		a[i]=read(),b[i]=a[i];
	sort(b+1,b+n+1);
	int len=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=n;++i){
		int pos=lower_bound(b+1,b+len+1,a[i])-b;
		a[i]=pos;
	} //离散化
	for(int i=1;i<=n;++i){
		int x=a[i];
		ans+=query(1,x+1,M);//找比这个数大的数的出现的总次数
		updata(1,x);//这个数出现次数+1
	}
	printf("%d",ans);
	return 0;
}

###打完收工
hiahiahia~

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值