HDOJ 5592 ZYB's Premutation(逆序对变题:树状数组+二分||线段树)

本文介绍了一种通过已知逆序对数量恢复原始排列的算法实现,使用树状数组和线段树两种数据结构进行高效查找。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


ZYB's Premutation

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit:131072/131072 K (Java/Others)
Total Submission(s): 1051    Accepted Submission(s): 544

Problem Description

ZYhas a premutationP,buthe only remeber the reverse log of each prefix of the premutation,now he askyou to
restore the premutation.

Pair
(i,j)(i<j)is considered as a reverse log if Ai>Ajis matched.

 

 

Input

In the first line there is the number of testcases T.

For each teatcase:

In the first line there is one number
N.

In the next line there are
NnumbersAi,describethe number of the reverse logs of each prefix,

The input is correct.

1T5,1N50000

 

 

Output

For each testcase,print the ans.

 

 

Sample Input

1

3

0 1 2

 

 

Sample Output

3 1 2

 

题意:有一个排列p,里面的数是1~n,现在分别给出【1,n】(1<=i<=n)范围内逆序对的数目,求排列的方式。

分析:我们可以逆向考虑(因为正向的话由于第一位的逆序对数一定是0,算不出什么),对于第i个数,它使逆序对的数量增加了temp=num[i]-num[i-1],即区间【1,i-1】内比这个数大的有temp个,即它在i个数中从小到大排在(i-temp)个,那么找到这个数即可。可以用树状数组或线段树


线状数组:先初始化每个位置的val都为1,getsum(i)就能得到位置在i及其前面的数的个数(小于等于),然后利用二分的思想找到要求的值保存在数组中。

注意:由于一个数用过后就不能再用了,所以某一个数被找到后应该将val-1.

#include <cstdio>
#include <cmath>
#include <queue>
#include <stack>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define f(i,a,b) for(int i=(a);i<(b);++i)
#define rush() int T;scanf("%d",&T);while(T--)
typedef long long ll;
const int maxn= 500005;
const ll mod = 1e9+7;
const int INF = 0x3f3f3f3f;
const double eps = 1e-6;

int num[maxn];
int tree[maxn];
int ans[maxn];
int n;

int lowbit(int x)
{
    return x&(-x);
}

void add(int pos,int val)
{
    while(pos<maxn)
    {
        tree[pos]+=val;
        pos+=lowbit(pos);
    }
}

int getsum(int pos)
{
    int ans=0;
    while(pos>0)
    {
        ans+=tree[pos];
        pos-=lowbit(pos);
    }
    return ans;
}

int solve(int k)
{
    int ans;
    int l=1;
    int r=n;
    while(r-l>=0)
    {
        int m=(l+r)/2;
        if(getsum(m)>=k)
        {
            ans=m;
            r=m-1;
        }
        else l=m+1;
    }
    return ans;
}

int main()
{
    rush()
    {
        mst(tree,0);
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            add(i,1);
            scanf("%d",&num[i]);
        }
        for(int i=n;i>=1;i--)
        {
            int temp=num[i]-num[i-1];
            temp=i-temp;
            ans[i]=solve(temp);
            add(ans[i],-1);
        }
        for(int i=1;i<n;i++)
        {
            printf("%d ",ans[i]);
        }
        printf("%d\n",ans[n]);
    }
    return 0;
}

线段树的思路与上面的基本一致,附上代码

#include <cstdio>
#include <cmath>
#include <queue>
#include <stack>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define f(i,a,b) for(int i=(a);i<(b);++i)
#define rush() int T;scanf("%d",&T);while(T--)
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1

typedef long long ll;
const int maxn= 50005;
const ll mod = 1e9+7;
const int INF = 0x3f3f3f3f;
const double eps = 1e-6;

int num[maxn];
int ans[maxn];
int tree[maxn<<2];

void pushup(int rt)
{
    tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}

void build(int l,int r,int rt)
{
    if(l==r)
    {
        tree[rt]=1;
        return;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
    pushup(rt);
}

int query(int pos,int l,int r,int rt)
{
    if(l==r)
    {
        tree[rt]=0;
        return l;
    }
    int m=(l+r)>>1;
    int ans;
    if(tree[rt<<1]>=pos)  ans=query(pos,lson); //左区间的数的个数大于等于pos,所以一定在左区间里
    else ans=query(pos-tree[rt<<1],rson);      //如果在右区间里,说明它是右区间第(pos-左区间的数)小的
    pushup(rt);
    return ans;
}

int main()
{
    int n;
    rush()
    {
        scanf("%d",&n);
        build(1,n,1);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&num[i]);
        }
        for(int i=n;i>=1;i--)
        {
            int temp=num[i]-num[i-1];
            temp=i-temp;            //第temp小
            ans[i]=query(temp,1,n,1);
        }
        for(int i=1;i<n;i++)
        {
            printf("%d ",ans[i]);
        }
        printf("%d\n",ans[n]);
    }
    return 0;
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值