题目链接:https://codeforces.com/contest/1208/problem/D
题目大意:
有一个1到n的排列,每个数字只出现一次,现在给你一个与原序列有关的s序列,si表示在原序列中,下标小于i且值小于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;
}