权值线段树就是把线段树的每个点权,赋予一定的含义,比如数字出现的次数,数值前缀出现的次数,并用区间求和维护一个前缀信息,比如数字出现的次数,第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);
}
}
待更新