HDU 5592 區間第K大(線段樹)
阿新 • • 發佈:2019-02-02
題目:ZYB有一個排列P,但他只記得P中每個字首區間的逆序對數,現在他要求你還原這個排列. 輸入: 1 3 0 1 2 輸出: 3 1 2
題解:num[k]-num[k-1] 是排列中第k個數貢獻的逆序數對, 故 k-(num[k]-num[k-1])是第k個數在前k個數中的排名(從小到大)。 逆向確認排列,確認第N個數後,刪除這個數的值。 再確認第N-1個數,它在剩下的數中排名 N-1-(num[N-1]-num[N-2])。依此類推。 很容易用線段樹維護區間剩下數的個數,進而求得第K大。
#include <iostream> #include <stdio.h> #include <string.h> #include <string> #include <algorithm> using namespace std; const int maxn=200005; const int INF=1000000007; struct Node{ int left,right; int value; }node[maxn<<2]; int num[maxn]; int ans[maxn]; void build(int i,int L,int R){ node[i].left=L; node[i].right=R; if(L==R){ node[i].value=1; //表示存在 return; } int mid=L+((R-L)>>1); build(i<<1,L,mid); build(i<<1|1,mid+1,R); node[i].value=node[i<<1].value+node[i<<1|1].value; } int query(int i,int Key){ // 查詢第Key大數 //必須查到葉子節點 if(node[i].left==node[i].right&&node[i].value==Key) return node[i].right; // 如果左邊至少包含Key個數,查左子樹的第Key大數 else if(node[i<<1].value>=Key) return query(i<<1,Key); //如果左邊不夠Key個數,查右子樹的第Key-node[i<<1].value大 else return query(i<<1|1,Key-node[i<<1].value); } //這裡的更新相當於刪除確定的數 void update_line(int i,int L,int R,int change){ if(node[i].left>=L&&node[i].right<=R){ node[i].value=change; return ; } int p1,p2; int mid=(node[i].left+node[i].right)>>1; if(L>mid) update_line(i<<1|1,L,R,change); else if(R<(mid+1)) update_line(i<<1,L,R,change); else{ update_line(i<<1|1,L,R,change); update_line(i<<1,L,R,change); } node[i].value=node[i<<1].value+node[i<<1|1].value; } int main() { int T,N,k; int a,b,c; scanf("%d",&T); while(T--){ scanf("%d",&N); build(1,1,N); for(int i=1;i<=N;i++){ scanf("%d",&num[i]); } num[0]=0; for(int i=N;i>=1;i--){ int cnt=i-(num[i]-num[i-1]); ans[i]=query(1,cnt); // 詢問剩餘數中的第cnt大數 update_line(1,ans[i],ans[i],0); //相當於刪除已經確認的數 } for(int i=1;i<N;i++) printf("%d ",ans[i]); printf("%d\n",ans[N]); } }