[codeforces1208D]Restore Permutation(树状数组+二分)

9 篇文章 0 订阅
8 篇文章 0 订阅

题目链接:https://codeforces.com/contest/1208/problem/D

题目大意:

有一个1到n的排列,每个数字只出现一次,现在给你一个与原序列有关的s序列,si表示在原序列中,下标小于i且值小于i的元素之和,即Si=\sum_{j=1}^{i-1}a[j](a[j]<a[i])

现在要你通过si恢复原序列。

思路:

考虑从后往前推。如果我们要计算最后一位,该怎么计算呢?最后一位的s[i]必然是1到x的一个前缀和,这个x就是答案。

那么我们计算出x,这个x就可以从原集合中去除,那么当我们计算倒数第二位的时候,该怎么计算呢?

我们可以维护一个不包含x的从1到n的前缀和,第一个大于s[n-1]的位置就是答案。

依次类推,当我们计算a[i]的时候,i+1到n的元素都被我们去除了,只需要知道在剩下的元素中前缀和第一个大于s[i]的位置在哪里

所以我们用树状数组维护前缀和,同时支持添加和删除操作(删除操作就是添加一个相反数)

具体用样例分析。


首先,计算最后一位,当前的s[i]=10,1到5的数字都没有出现过,所以在[1,2,3,4,5]中二分查找第一个前缀和大于10的位置,答案为5,那么就将5删除掉,剩下的集合为[1,2,3,4]

然后计算倒数第二位,当前的s[i]=1,二分查找到第一个前缀和大于1的位置,为2,所以a[n-1]=2,删除2,剩余序列变为[1,3,4]

依次推就可以了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn=2e5+10;
int a[maxn];
ll s[maxn];
int n;
ll c[maxn+5];
inline int lowbit(int x){
    return x&(-x);
}
inline void add(int x,int v){
    for(int i=x;i<=n;i+=lowbit(i)){
        c[i]+=v;
    }
}
inline ll sum(int x){
    ll ans=0;
    for(int i=x;i>0;i-=lowbit(i)) ans+=c[i];
    return ans;
}

//二分查找第一个大于s[i]的前缀和位置
int binarysearch(ll x){
    int L=1,R=n;
    int ans=0;
    while(L<=R){
        int mid=(L+R)>>1;
        ll Sum=sum(mid);
        if(Sum<=x){
            L=mid+1;
        }
        else{
            R=mid-1;
            ans=mid;
        }
    }
    return ans;
}
signed main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%I64d",&s[i]);
    }
    for(int i=1;i<=n;i++){
        add(i,i);
    }

    for(int i=n;i>=1;i--){
        int ans=binarysearch(s[i]);
        a[i]=ans;
        add(a[i],-a[i]);
    }
    for(int i=1;i<=n;i++){
        printf("%d ",a[i]);
    }
    puts("");
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值