NOIP模拟 排列【权值线段树】

题目大意:

对于一个1到n的排列,若知道每一位的逆序数(第i位a[i]的逆序数就是a[1]~a[i-1]中比a[i]大的数的个数),则能求出原排列。
现在对于排列{a[i]},给出{p[i]}。p[i]表示a[1]~a[i]的逆序数和。请你求出原排列。(1<=n<=100000)

解题思路:

先求出每个数的逆序数,仍设为p[i],倒着确定,那对于最后一个未确定的数a[i]来说,它前面有p[i]个比它大的数,所以他就是剩下未确定的中的第i-p[i]小的数,用权值线段树维护即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#define ll long long
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

ll getll()
{
    ll i=0,f=1;char c;
    for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=i*10+c-'0';
    return i*f;
}

const int N=100005;
int n,a[N],tr[N<<2];
ll p[N];

void build(int k,int l,int r)
{
    if(l==r)
    {
        tr[k]=1;
        return;
    }
    int mid=l+r>>1;
    build(k<<1,l,mid),build(k<<1|1,mid+1,r);
    tr[k]=tr[k<<1]+tr[k<<1|1];
}

int query(int k,int l,int r,int num)
{
    if(l==r)
    {
        tr[k]=0;
        return l;
    }
    int mid=l+r>>1,res;
    if(num<=tr[k<<1])res=query(k<<1,l,mid,num);
    else res=query(k<<1|1,mid+1,r,num-tr[k<<1]);
    tr[k]=tr[k<<1]+tr[k<<1|1];
    return res;
}

int main()
{
    //freopen("premu.in","r",stdin);
    //freopen("premu.out","w",stdout);
    n=getint();
    for(int i=1;i<=n;i++)p[i]=getll();
    for(int i=n;i;i--)p[i]-=p[i-1];
    for(int i=1;i<=n;i++)p[i]=i-p[i];
    build(1,1,n);
    for(int i=n;i;i--)
        a[i]=query(1,1,n,p[i]);
    for(int i=1;i<=n;i++)
        cout<<a[i]<<' ';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值