树状数组—求第k小的数—离散化

本文介绍如何通过离散化方法优化树状数组的空间占用,实现求解第k小的数的问题。适用于权值较大的场景,通过离散化压缩权值范围,使树状数组能更高效地处理大数据量。
树状数组也求第k小的数 不会? 虽然码量小,速度快,但有缺点。因为它建的是权值线段树,所以如果权值太大,它就无法正常运作。
有一种方法可以解决零散的大数,那就是离散化。本篇文章就来介绍一下用离散化优化其空间的树状数组。


思路

把所有数离散化,该离散化只需要能保留数字排名的前后即可,对区间信息等不作要求。

离散之后,按照离散后的权值插入树状数组,用这个树状数组就可以求出第k小的数的离散值了,实际值再转换一下就好了。

在离散化后,原本很大的权值就能被压缩,因为树状数组是按权值建的,于是它的大小就能大大减小。加上离散化的树状数组求第k大,能更好的适应更大的数据。


例题

大佬题面:

为了大家在机房不太无聊,不知道是谁弄来了点砖头,可能是为了让大家平时练一练空手劈砖头?AKC老师很开心地抢到
了很多砖头,而且把他们叠成很多个柱子,因为他法力无边,劈一个砖头实在是太无趣了。但是破坏王欧老师又上线了,

不过他这次是把C老师的砖头柱弄得参差不齐,处女座的C老师希望在这N柱砖里面有连续的K柱的高度是一样的。

你可以选择以下两个动作
1.从某柱砖的顶端拿一块砖出来,丢向欧老师.
2.从欧老师手中抢来一块砖,放到任意一柱.欧老师有的砖无限多.

现在希望用最小次数的动作完成任务.

简易题面:

给一个长为N的序列,找出其中一段长为K的子序列,使其中各个数与中位数的差的总和最小,求这个总和。


正解

树状数组

要求中位数,其实也可以转换成求第k小的数,所以树状数组直接上。

枚举所有的符合大小的区间,找到中位数后,再开一个树状数组来求区间和。判断其是否为最优解即可。

为了能使树状数组的求知等更加方便,减小树状数组的空间,我们给数据进行离散。


代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100010;
int bin[30];


int n;
int h[maxn],ys[maxn],yss[maxn];//ys[真值]=离散值, yss[离散值]=真值 
long long s1[maxn],s2[maxn];//s1用于记录数字的个数,s2用于求一段权值区间内的权值和 


int lowbit(int x)
{
	return x&-x;
}


void change(long long s[],int x,int c)
{
	for(;x<=n;x+=lowbit(x))
	{
		s[x]+=c;
	}
}


long long getsum(long long s[],int x)
{
	long long sum=0;
	for(;x>=1;x-=lowbit(x))
	{
		sum+=s[x];
	}
	return sum;
}


int erfen(int x)
{
	int l=1,r=n,ans;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(yss[mid]<=x)
		{
			l=mid+1;
			ans=mid;
		}
		else
		{
			r=mid-1;
		}
	}
	return ans;
}


int query(int k)
{
	int now=0,sum=0;//now是中位数的数值,sum统计在now左边出现了几个数
	for(int i=20;i>=0;i--)
	{
		if(now+bin[i]<=n && sum+s1[now+bin[i]]<k)
		{
			now+=bin[i];
			sum+=s1[now];
		}
	}
	return now+1;
}


int main()
{
	bin[0]=1;for(int i=1;i<=20;i++) bin[i]=bin[i-1]*2;
	int k;scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&h[i]);
		yss[i]=h[i];
	}
	
	sort(yss+1,yss+n+1);
	for(int i=1;i<=n;i++) ys[i]=erfen(h[i]);
	for(int i=1;i<=n;i++) yss[ys[i]]=h[i];
	
	for(int i=1;i<k;i++)
	{
		change(s1,ys[i],1);
		change(s2,ys[i],h[i]);//个数统计和区间求值都按离散权值插入
	}
	
	long long ans=((long long)1<<62)-1;
	for(int i=k;i<=n;i++)
	{
		change(s1,ys[i],1);
		if(i!=k) change(s1,ys[i-k],-1);
		change(s2,ys[i],h[i]);
		if(i!=k) change(s2,ys[i-k],-h[i-k]);
		
		int mid=query((k+1)/2);
		
		long long hs=0;
		hs+=getsum(s1,mid-1)*yss[mid]-getsum(s2,mid-1);
		hs+=(getsum(s2,n)-getsum(s2,mid))-(long long)(getsum(s1,n)-getsum(s1,mid))*yss[mid];
		if(hs<ans) ans=hs;
	}
	printf("%lld\n",ans);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值