题面
题意:
一个长度为 n n n的排列 a a a,现在定义 p i p_i pi为数组 a a a中下标小于等于 i i i并且小于 a i a_i ai的数字的和。现在给定 p p p,求 a a a。
思路:
首先可以肯定的是,
p
p
p中最后一个
0
0
0出现的位置
p
o
s
pos
pos在
a
a
a中一定是
1
1
1。我们可以反证:
假设
a
p
o
s
a_{pos}
apos不为
1
1
1,假设
1
1
1在
p
o
s
pos
pos之前,那么
p
p
o
s
≥
1
p_{pos} \ge 1
ppos≥1;假设
1
1
1在
p
o
s
pos
pos之后,那么
p
o
s
pos
pos一定不是最后一个
0
0
0出现的位置,因为当
a
p
o
s
=
1
a_{pos}=1
apos=1,那么
p
p
o
s
p_{pos}
ppos一定等于
0
0
0,因此最后一个
0
0
0出现的位置一定是
1
1
1,那么我们把
1
1
1放到
p
o
s
pos
pos处。并且把
i
i
i之后的
p
j
p_{j}
pj都减去
1
1
1.
其实对于从 2 2 2到 n n n的处理方法都一样,对于第 i i i次操作,找到 p p p中最后一个为 0 0 0的位置 p o s pos pos,令 a p o s a_{pos} apos为 i i i,然后对于所有的 i ≥ p o s i \ge pos i≥pos,让 p i − i p_{i}-i pi−i。最终即可得到 a a a数组。对于上面的操作,可以用线段树。
线段树寻找最后一个
0
0
0的位置:从根节点出发,先看右子树的最小值是否为
0
0
0,如果是,去查右子树,否则查左子树。
最终复杂度
O
(
n
⋅
(
l
o
g
(
n
)
)
2
)
O(n \cdot (log(n))^2)
O(n⋅(log(n))2)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+100;
const int mod = 1e9+7;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const ll llINF = 0x3f3f3f3f3f3f3f3f;
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define fep(i,a,b) for(int i=(a);i>=(b);i--)
inline bool read(ll &num) {
char in;bool IsN=false;
in=getchar();
if(in==EOF) return false;
while(in!='-'&&(in<'0'||in>'9')) in=getchar();
if(in=='-'){ IsN=true;num=0;}
else num=in-'0';
while(in=getchar(),in>='0'&&in<='9'){
num*=10,num+=in-'0';
}
if(IsN) num=-num;
return true;
}
ll minn[N],sum[N],tag[N],p[N],a[N],n;
void pushup(ll rt){
minn[rt]=min(minn[rt<<1|1],minn[rt<<1]);
return ;
}
void pushdown(ll rt,ll l,ll r){
ll mid=(l+r)>>1;
tag[rt<<1]+=tag[rt];
tag[rt<<1|1]+=tag[rt];
minn[rt<<1]+=tag[rt];
minn[rt<<1|1]+=tag[rt];
tag[rt]=0;
}
void buildtree(ll rt,ll l,ll r){
tag[rt]=0;
if(l==r){
minn[rt]=p[l];
return ;
}
int mid=(l+r)>>1;
buildtree(rt<<1,l,mid);
buildtree(rt<<1|1,mid+1,r);
pushup(rt);
}
void modify(ll nl,ll nr,ll l,ll r,ll rt,ll k){//区间加减
if(nl<=l&&r<=nr){
tag[rt]+=k;
minn[rt]+=k;
return ;
}
ll mid=(l+r)>>1;
if(nl<=mid) modify(nl,nr,l,mid,rt<<1,k);
if(nr>mid) modify(nl,nr,mid+1,r,rt<<1|1,k);
pushup(rt);
}
ll ask(ll rt,ll l,ll r,ll nl,ll nr){//查询区间最小值
if(nl<=l&&r<=nr){
return minn[rt];
}
pushdown(rt,l,r);
ll mid=(l+r)>>1;
ll ans=9999999999;
if(nl<=mid) ans=min(ans,ask(rt<<1,l,mid,nl,nr));
if(nr>mid) ans=min(ans,ask(rt<<1|1,mid+1,r,nl,nr));
return ans;
}
ll query(ll rt,ll l,ll r){//查找最后一个0的位置
if(l==r){
return l;
}
ll mid=(l+r)>>1;
if(ask(1,1,n,mid+1,r)==0) return query(rt<<1|1,mid+1,r);
else return query(rt<<1,l,mid);
pushup(rt);
}
int main(){
read(n);
rep(i,1,n) read(p[i]);
buildtree(1,1,n);
rep(i,1,n){
int id=query(1,1,n);
a[id]=i;
modify(id+1,n,1,n,1,-i);
modify(id,id,1,n,1,100000000000+i);//找到位置后把这个变成大数
}
rep(i,1,n) cout<<a[i]<<' ';
cout<<endl;
return 0;
}