權值線段樹就是把線段樹的每一個點權,賦予必定的含義,好比數字出現的次數,數值前綴出現的次數,並用區間求和維護一個前綴信息,好比數字出現的次數,第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; }