给定一个长度为n的s序列,(s_{i})表示前i-1个数中有比i小的数的总和,输出原序列p(1-n每个数出现一次)
解法一 树状数组:
倒序,对于每个(s_{i}),找出从1-n中的未被利用且和为(s_{i})的前缀和,则(p_{i})为这些数中最大的数+1,每次找到后需要及时删去
此处查找使用倍增的方法。
数组数组+倍增,单次操作O(log n)
树状数组+二分,单次操作O(log n*log n)
第三组样例过程:
5
0 1 1 1 10
1 3 3 10 5
1 3 3 10 0
1 1 3 8 0
1 1 0 5 0
1 1 0 1 0
0 0 0 0 0
输出:
1 4 3 2 5
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll s[200050], c[200050];
ll p[25];
int n, t, h[200050];
void add(int x, int t)
{
while (x <= n)
{
c[x] -= t;
x += x&-x;
}
}
int main()
{
p[0] = 1;
for (int i = 1; i<22; i++)p[i] = p[i - 1] << 1;
scanf("%d",&n);
t = log(n) / log(2);
for (int i = 1; i <= n; i++)
{
scanf("%lld",&s[i]);
c[i] += i;
if (i + (i&-i) <= n)c[i + (i&-i)] += c[i];
}
// for(int i=1;i<=n;i++)
// cout<<c[i]<<" ";
// cout<<endl;
for (int i = n; i; i--)
{
int ans = 0; ll sum = 0;
for (int j = t; j >= 0; j--) {
if (ans + p[j] <= n && sum + c[ans + p[j]] <= s[i] ) {
sum += c[ans + p[j]];
ans += p[j];
}
}
h[i] = ans+1;
add(ans+1, ans+1);
// for(int i=1;i<=n;i++)
// cout<<c[i]<<" ";
// cout<<endl;
}
for (int i = 1; i <= n; i++) printf("%d ", h[i]);
puts("");
return 0;
}
解法二:线段树
#include<bits/stdc++.h>
#pragma GCC optimize(3)
#define max(a,b) a>b?a:b
using namespace std;
typedef long long ll;
const int N=2e5+5;
ll tr[N<<2];
ll s[N];
int pos[N];
void build(int l,int r,int p){
if(l==r){
tr[p]=l;
return ;
}
int mid=(l+r)>>1;
build(l,mid,p<<1);
build(mid+1,r,p<<1|1);
tr[p]=tr[p<<1]+tr[p<<1|1];
}
int query(int l,int r,int p,ll val){
if(l==r){
tr[p]=0;
return l;
}
int mid=(l+r)>>1;
int ans=0;
if(tr[p<<1]>val){
ans=query(l,mid,p<<1,val);
tr[p]-=ans;
}
else{
ans=query(mid+1,r,p<<1|1,val-tr[p<<1]);
tr[p]-=ans;
}
return ans;
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%I64d",&s[i]);
build(1,n,1);
for(int i=n;i>=1;i--){
pos[i]=query(1,n,1,s[i]);
}
for(int i=1;i<=n;i++) printf("%d%c",pos[i],"
"[i==n]);
return 0;
}
此题与POJ 2182类似。