Restore Permutation(线段树)

[codeforces 1208D] Restore Permutation(线段树)

题面

题意:

一个长度为$n$的排列$a$,现在定义$p_i$为数组$a$中x下标小于等于$i$并且小于$a_i$的数字的和。现在给定$p$,求$a$。

思路:

首先可以肯定的是,$p$中最后一个$0$出现的位置$pos$在$a$中一定是$1$。我们可以反证:
假设$a_{pos}$不为$1$,假设$1$在$pos$之前,那么$p_{pos} \ge 1$;假设$1$在$pos$之后,那么$pos$一定不是最后一个$0$出现的位置,因为当$a_{pos}=1$,那么$p_{pos}$一定等于$0$,因此最后一个$0$出现的位置一定是$1$,那么我们把$1$放到$pos$处。并且把$i$之后的$p_{j}$都减去$1$.

其实对于从$2$到$n$的处理方法都一样,对于第$i$次操作,找到$p$中最后一个为$0$的位置$pos$,令$a_{pos}$为$i$,然后对于所有的$i \ge pos$,让$p_{i}-i$。最终即可得到$a$数组。对于上面的操作,可以用线段树。

线段树寻找最后一个$0$的位置:从根节点出发,先看右子树的最小值是否为$0$,如果是,去查右子树,否则查左子树。
最终复杂度$O(n \cdot (log(n))^2)$

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#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;
}