HDU 5592 区间第K大(线段树)

题目:ZYB有一个排列PP,但他只记得PP中每个前缀区间的逆序对数,现在他要求你还原这个排列.             输入:
      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]);
    }
}


                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值