权值线段树就是把线段树的每一个点权,赋予必定的含义,好比数字出现的次数,数值前缀出现的次数,并用区间求和维护一个前缀信息,好比数字出现的次数,第K大等(不能实现区间第K大),前缀第K大等。node
可以比较容易实现平衡树的一系列操做ios
一个序列中,插入一个数,删除一个数,求值为数的排名,查询第K小的数,求比这个数小的数,求比这个数大的数。算法
上述操做均可以经过权值线段树实现。可是须要注意的是,上述操做数的范围若是过大,那么权值线段树将开不下,由于权值线段树存储的是节点的单点信息,也就是从1-N开始的序列,须要把全部操做的值进行存储,并进行离散化。这样权值线段树某种程度上说也就是离线的算法了。数据结构
下面对我所作过的权值线段树题目进行总结,提供关键代码ide
您须要写一种数据结构(可参考题目标题),来维护一些数,其中须要提供如下操做:ui
1插入x数spa
2删除x数(如有多个相同的数,因只删除一个)3d
3查询x数的排名(排名定义为比当前数小的数的个数+1。如有多个相同的数,因输出最小的排名)code
4查询排名为x的数blog
5求x的前驱(前驱定义为小于x,且最大的数)
6求x的后继(后继定义为大于x,且最小的数)
思路:
离散化:
因为这个题目的x范围很是大,须要先把询问记录下来,并进行离散化
查询第K小的数:
这个能够利用权值线段树存储的是数出现的次数,维护区间内数字出现的次数,查询左右子树数字出现的次数,若是左儿子数字出现次数和是小于K,表明这个第K小在右子树,可是它在右子树的排名是K减去左子树儿子的个数,这样不断往下进行查找,当查找到单点的时候,就是第K小。
int Kth(int rt,int k){
int l=tree[rt].l;
int r=tree[rt].r;
if(l==r){
return l;
}
int mid=(l+r)>>1;
if (tree[lson].sum>=k){
return Kth(lson,k);
}else {
return Kth(rson,k-tree[lson].sum);
}
查询x数的排名:
能够利用权值线段树存储的数字个数的信息,查询[1,x-1]区间内部数字出现的个数+1,直接区间查询便可
求x的前驱:
咱们能够求在[1,x-1]区间内部数字出现的次数记为k,那么第k大其实就是前面最靠近x的数,也就是前驱
求x的后继:
咱们能够求在[1,x]区间内部数字出现的的次数为k,那么第k+1大其实就是后面最靠近x的数,也就是后继。
代码
#include<iostream> #include<stdio.h> #include<string.h> #include<vector> #include<algorithm> #define LL long long #define rep(i,j,k) for(int i=j;i<=k;i++) #define per(i,j,k) for(int i=j;i>=k;i--) #define pb push_back #define lson rt<<1 #define rson rt<<1|1 using namespace std; const int maxx = 100005; struct node{ int l,r,sum; }tree[maxx<<2]; int op[maxx]; int a[maxx]; vector<int>v; int getid(int x){ return lower_bound(v.begin(),v.end(),x)-v.begin()+1; } void buildtree(int rt,int l,int r){ tree[rt].l=l; tree[rt].r=r; tree[rt].sum=0; if (l==r){ return; } int mid=(l+r)>>1; buildtree(lson,l,mid); buildtree(rson,mid+1,r); } void update(int rt,int pos,int w){ int l=tree[rt].l; int r=tree[rt].r; if (l==r){ tree[rt].sum+=w; return; } int mid=(l+r)>>1; if(pos<=mid){ update(lson,pos,w); }else{ update(rson,pos,w); } tree[rt].sum=tree[lson].sum+tree[rson].sum; } int query(int rt,int ql,int qr){ if(ql>qr)return 0; int l=tree[rt].l; int r=tree[rt].r; if(ql<=l && r<=qr){ return tree[rt].sum; } int mid=(l+r)>>1; if (qr<=mid){ return query(lson,ql,qr); }else if (ql>mid){ return query(rson,ql,qr); }else { return query(lson,ql,mid)+query(rson,mid+1,qr); } } int Kth(int rt,int k){ int l=tree[rt].l; int r=tree[rt].r; if(l==r){ return l; } int mid=(l+r)>>1; if (tree[lson].sum>=k){ return Kth(lson,k); }else { return Kth(rson,k-tree[lson].sum); } } int main(){ int t; int x; scanf("%d",&t); rep(i,1,t){ scanf("%d%d",&op[i],&a[i]); if(op[i]!=4) v.pb(a[i]); } sort(v.begin(),v.end()); v.erase(unique(v.begin(),v.end()),v.end()); int tot=v.size(); buildtree(1,1,tot); rep(i,1,t){ if(op[i]==1){ update(1,getid(a[i]),1); }else if (op[i]==2){ update(1,getid(a[i]),-1); }else if (op[i]==3){ printf("%d\n",query(1,1,getid(a[i])-1)+1); }else if (op[i]==4){ printf("%d\n",v[Kth(1,a[i])-1]); }else if (op[i]==5){ int pre=query(1,1,getid(a[i])-1); printf("%d\n",v[Kth(1,pre)-1]); }else{ int bac=query(1,1,getid(a[i])); printf("%d\n",v[Kth(1,bac+1)-1]); } } return 0; }
给你一个序列,你能够循环左移,问最小的逆序对是多少???
逆序对实际上是寻找比这个数小的数字有多少个,这个问题其实正是权值线段树所要解决的
咱们把权值线段树的单点做为1-N的数中每一个数出现的次数,并维护区间和,而后从1-N的数,在每一个位置,查询比这个数小的数字的个数,这就是当前位置的逆序对,而后把当前位置数的出现的次数+1,就能获得答案。
而后咱们考虑循环右移。咱们每次循环右移,至关于把序列最左边的数字给放到最右边,而位于序列最左边的数字,它对答案的功效仅仅是这个数字大小a[i]-1,由于比这个数字小的数字所有都在它的后面,而且这个数字放到最后了,它对答案的贡献是N-a[i],由于比这个数字大数字所有都在这个数字的前面,因此每当左移一位,对答案的贡献其实就是
Ans=Ans-(a[i]-1)+n-a[i]
因为数字从0开始,咱们建树从1开始,咱们把全部数字+1便可
#include<iostream> #include<string.h> #include<algorithm> #include<stdio.h> using namespace std; const int maxx = 5005; int tree[maxx<<2]; inline int L(int root){return root<<1;}; inline int R(int root){return root<<1|1;}; inline int MID(int l,int r){return (l+r)>>1;}; int a[maxx]; void update(int root,int l,int r,int pos){ if (l==r){ tree[root]++; return; } int mid=MID(l,r); if (pos<=mid){ update(L(root),l,mid,pos); }else { update(R(root),mid+1,r,pos); } tree[root]=tree[L(root)]+tree[R(root)]; } int query(int root,int l,int r,int ql,int qr){ if (ql<=l && r<=qr){ return tree[root]; } int mid=MID(l,r); if (qr<=mid){ return query(L(root),l,mid,ql,qr); }else if (ql>mid){ return query(R(root),mid+1,r,ql,qr); }else { return query(L(root),l,mid,ql,qr)+query(R(root),mid+1,r,ql,qr); } } int main(){ int n; while(~scanf("%d",&n)){ int ans=0; memset(tree,0,sizeof(tree)); for (int i=1;i<=n;i++){ scanf("%d",&a[i]); a[i]++; ans+=query(1,1,n,a[i],n); update(1,1,n,a[i]); } int minn=ans; for (int i=1;i<=n;i++){ ans=ans+(n-a[i]+1)-a[i]; minn=min(ans,minn); } printf("%d\n",minn); } return 0; }
POJ – 2104
给你一个序列,这个序列表明原来序列的[1,i]位置的 前缀逆序对数目,原序列是什么
对于逆序对问题,咱们应该从边界开始入手,考虑最右边,咱们咱们能够经过a[n]-a[n-1]
算到n位置的逆序对,也就是前面比当前位置的数字大的个数,那么这个位置的数必定是
n-a[n]-a[n-1].
咱们思考一下可不能够推广呢?实际上是没问题的
咱们考虑从后往前,咱们容易知道权值线段树能够查询第K小
那么初始化所有的权值线段树的单点所有为1
那么从后往前,咱们查询前i位置的第i-a[i]-a[i-1]小,那么这个数就是当前位置的数
并把权值线段树的这个数的出现的次数变为0,这样就消除了,后面已经出现过的数,前面的第xx小的影响。
#include<iostream> #include<stdio.h> #include<string.h> #include<algorithm> #include<vector> using namespace std; const int maxx = 50005; int a[maxx]; int ans[maxx]; struct node{ int l,r,w; }tree[maxx<<2]; inline int L(int root){return root<<1;}; inline int R(int root){return root<<1|1;}; inline int MID(int l,int r){return (l+r)>>1;}; void buildtree(int root,int l,int r){ tree[root].l=l; tree[root].r=r; if (l==r){ tree[root].w=1; return; } int mid=MID(l,r); buildtree(L(root),l,mid); buildtree(R(root),mid+1,r); tree[root].w=tree[L(root)].w+tree[R(root)].w; } void update(int root,int pos){ int l=tree[root].l; int r=tree[root].r; if (l==r){ tree[root].w=0; return; } int mid=MID(l,r); if (pos<=mid){ update(L(root),pos); }else { update(R(root),pos); } tree[root].w=tree[L(root)].w+tree[R(root)].w; } int query(int root,int ql,int qr,int k){ int l=tree[root].l; int r=tree[root].r; if(l==r){ return l; }; int mid=MID(l,r); if (tree[L(root)].w>=k){ return query(L(root),ql,qr,k); }else { return query(R(root),ql,qr,k-tree[L(root)].w); } } int main(){ int t; scanf("%d",&t); int n; while(t--){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); } buildtree(1,1,n); for (int i=n;i>=1;i--){ int tmp=a[i]-a[i-1]; ans[i]=query(1,1,n,i-tmp); update(1,ans[i]); } for(int i=1;i<=n;i++){ if (i-1)printf(" %d",ans[i]); else printf("%d",ans[i]); } printf("\n"); } return 0; }
I命令 I_k 新建一个工资档案,初始工资为k。
若是某员工的初始工资低于工资下界,他将马上离开公司。
A命令 A_k 把每位员工的工资加上k
S命令 S_k 把每位员工的工资扣除k
F命令 F_k 查询第k多的工资
注意:员工发现本身的工资已经低于了合同规定的工资下界,他就会马上离开
对于操做F,如何人数不够,输出-1,最后输出离开的总人数
I命令的条数不超过100000
A命令和S命令的总条数不超过100
F命令的条数不超过100000
每次工资调整的调整量不超过1000
新员工的工资不超过100000
询问强制在线
咱们能够考虑创建权值线段树,咱们记录一个add表明涨工资的量,nowmin表明如今的最低工资。
考虑命令I,咱们须要知道初始工资下界是最开始的nowmin(由于它没有享受到涨工资)
所为了消除涨工资的影响,初始工做k知足k+add>=nowmin
考虑命令A,记录一个add+=w,nowmin-=w(当前每一个人最低工资下界将下降)咱们暂时记录这个值,不作更新。由于确定没有人从这个操做中会由于工资过低而走人
考虑命令S,咱们更新add-=w,nowmin+=w(最低工资下界上升),那么可能会有人从中被淘汰。咱们对权值线段树的[1,nowmin]区间修改所有赋值为0并打上laze标记
考虑命令F,直接查询第K大便可。
因为有可能最低工资下界会变为负数,用动态开点就不用担忧了
代码
#include<iostream> #include<stdio.h> #include<algorithm> #include<string.h> #define rep(i,j,k) for(int i=j;i<=k;i++) #define per(i,j,k) for(int i=j;i>=k;i--) using namespace std; const int maxx = 2e5+50; int lson[maxx*18],rson[maxx*18],sum[maxx*18]; int tot=1,root=1; bool laze[maxx*18]; void push_down(int x){ if(laze[x]){ if(!lson[x])lson[x]=++tot; if(!rson[x])rson[x]=++tot; sum[lson[x]]=0; sum[rson[x]]=0; laze[lson[x]]=1; laze[rson[x]]=1; laze[x]=0; } } void inserts(int l,int r,int x,int &p){ if(!p)p=++tot; if (l==r){ sum[p]++; return; } int mid=(l+r)>>1; push_down(p); if (x<=mid){ inserts(l,mid,x,lson[p]); }else { inserts(mid+1,r,x,rson[p]); } sum[p]=sum[lson[p]]+sum[rson[p]]; } void update(int l,int r,int ul,int ur,int &p){ if (!p)p=++tot; if (ul<=l && r<=ur){ sum[p]=0; laze[p]=1; return; } push_down(p); int mid=(l+r)>>1; if(ul<=mid)update(l,mid,ul,ur,lson[p]); if(ur>mid)update(mid+1,r,ul,ur,rson[p]); sum[p]=sum[lson[p]]+sum[rson[p]]; } int kth(int l,int r,int k,int &p){ if(l==r){ return l; } int mid=(l+r)>>1; push_down(p); if(k<=sum[lson[p]]){ return kth(l,mid,k,lson[p]); }else { return kth(mid+1,r,k-sum[lson[p]],rson[p]); } } int main(){ int n,m,w; int add=0,nowmin,num=0; char op[10]; while(~scanf("%d%d",&n,&nowmin)){ rep(i,1,n){ scanf("%s%d",op,&w); if (op[0]=='I'){ //减去影响后若是起薪小于工资下界 if (w-add<nowmin)continue; //他的工资实际上应该是他的起 inserts(-maxx,maxx,w-add,root); num++; }else if(op[0]=='A'){ //偏移应该加上 add+=w; //同时此时要求最低的工资下降 nowmin-=w; }else if(op[0]=='S'){ add-=w; nowmin+=w; //比最低工资低的值所有赋值为0 update(-maxx,maxx,-maxx,nowmin-1,root); }else { //若是全部人被开除 if(w>sum[1]){ printf("-1\n"); continue; }else { //查询剩下的第K大,而且要加上偏移 //其实等价于查询当前第K大=总数-第K小+1 printf("%d\n",kth(-maxx,maxx,sum[1]-w+1,root)+add); } } } printf("%d\n",num-sum[1]); } return 0; }
题意:
有一个树林,树林中不一样种类的树有不一样的数量,高度,砍伐它们的价格。如今要求砍掉一些树,使得高度最高的树占剩下的树的总数的一半以上,求最小花费
首先咱们应该从低到高枚举每一种高度的树,这里须要作一个离散化。由于咱们须要先求出每一种高度树中,比他高的树的数目和所须要消耗的花费,为了方便求前缀和,以及把相同高度和不一样花费的树储存在一块儿,咱们把每一个高度小于等于高度的树的数目和砍掉的花费的前缀求出来。这样咱们可以O(1)的求出每一个高度对应所须要砍掉比这个树高度更高的树的数目和消费,以及比这个高度小的树的数目,以及当前高度树的数目。
咱们能够很方便的知道咱们须要砍伐数目最小须要多少,咱们暂时设定须要K个。
那么咱们须要寻找的是比这个树高度小的树中,前K小消费的和。想到这里,权值线段树已经呼之欲出了。
咱们离散化全部的消耗,按照消耗进行创建权值线段树,线段树维护的是砍单个树消耗为某个值时,出现的次数,以及单点消耗的总和=单个树消耗*次数,并求区间和,维护区间信息。
咱们须要查询前K小的和。当每次枚举一个高度之后,把全部这个高度的树,加入权值线段树中,更新线段树。这样问题就解决了。
询问前k小
LL query(int root,int l,int r,LL k){
if (l==r)return vcost[l-1]*k;//单点就直接算
if (tree[root].cnt==k)return tree[root].w;//若是内部个数正好为K个直接返回w
int mid=MID(l,r);
if (tree[L(root)].cnt>=k){
return query(L(root),l,mid,k);
}else {
//须要返回左子树的和同时查询右子树
return tree[L(root)].w+query(R(root),mid+1,r,k-tree[L(root)].cnt);
}
}
代码
#include<iostream> #include<string.h> #include<algorithm> #include<stdio.h> #include<vector> #define LL long long #define pii pari<int,int> #define pb push_back #define rep(i,j,k) for(int i=j;i<=k;i++) #define per(i,j,k) for(int i=j;i>=k;i--) using namespace std; const int maxx = 1e5+6; inline int L(int root){return root<<1;}; inline int R(int root){return root<<1|1;}; inline int MID(int l,int r){return (l+r)>>1;}; vector<LL>vh; vector<LL>vcost; struct node{ int l,r; LL cnt; LL w; }tree[maxx<<2]; struct Node{ LL h,cost,num; }a[maxx]; LL sumc[maxx]; LL sumn[maxx]; int gethight(LL x){ return lower_bound(vh.begin(),vh.end(),x)-vh.begin()+1; } int getcost(int x){ return lower_bound(vcost.begin(),vcost.end(),x)-vcost.begin()+1; } void update(int root,int l,int r,int pos,LL cnt,LL val){ if (l==r){ tree[root].cnt+=cnt; tree[root].w+=cnt*val; return ; } int mid=MID(l,r); if (pos<=mid){ update(L(root),l,mid,pos,cnt,val); }else { update(R(root),mid+1,r,pos,cnt,val); } tree[root].cnt=tree[L(root)].cnt+tree[R(root)].cnt; tree[root].w=tree[L(root)].w+tree[R(root)].w; } LL query(int root,int l,int r,LL k){ if (l==r)return vcost[l-1]*k; if (tree[root].cnt==k)return tree[root].w; int mid=MID(l,r); if (tree[L(root)].cnt>=k){ return query(L(root),l,mid,k); }else { return tree[L(root)].w+query(R(root),mid+1,r,k-tree[L(root)].cnt); } } bool cmp(Node a,Node b){ if (a.h==b.h){ if (a.cost==b.cost){ return a.num>b.num; } return a.cost<b.cost; } return a.h<b.h; } int main(){ int n; while(~scanf("%d",&n)){ vh.clear(); vcost.clear(); memset(sumc,0,sizeof(sumc)); memset(sumn,0,sizeof(sumn)); memset(tree,0,sizeof(tree)); rep(i,1,n){ scanf("%lld%lld%lld",&a[i].h,&a[i].cost,&a[i].num); vh.pb(a[i].h); vcost.pb(a[i].cost); } sort(vh.begin(),vh.end()); sort(vcost.begin(),vcost.end()); vh.erase(unique(vh.begin(),vh.end()),vh.end()); vcost.erase(unique(vcost.begin(),vcost.end()),vcost.end()); sort(a+1,a+1+n,cmp); rep(i,1,n){ a[i].h=gethight(a[i].h); sumc[a[i].h]+=(LL)a[i].num*a[i].cost; sumn[a[i].h]+=a[i].num; } int tot=vh.size(); rep(i,1,tot){ sumc[i]+=sumc[i-1]; sumn[i]+=sumn[i-1]; } LL cost,ans=1e18; int pos=1,j; rep(i,1,tot){ cost=sumc[tot]-sumc[i]; LL num=sumn[i]-sumn[i-1]; // cout<<"..."<<cost<<"..."<<num<<"..."<<sumn[i-1]<<endl; if(num>sumn[i-1]){ ans=min(ans,cost); }else { cost+=query(1,1,tot,sumn[i-1]-num+1); ans=min(ans,cost); } for (j=pos;j<=n && a[j].h==a[pos].h;j++){ update(1,1,tot,getcost(a[j].cost),a[j].num,a[j].cost); } pos=j; } printf("%lld\n",ans); } return 0; }