CF798E Mike and code of a permutation
讲一下自己看solution时的障碍.
题面:
对于一个排列 (P=[p_1,p_2,dots,p_n])
定义它的编码(A)为:对于每个 (i),找到第一个未被标记过的位置 (j) 满足 (p_j>p_i),令 (a_i=j),并给(j)打上标记;若没有合法的,则 (a_i=-1)。
现给出一个编码 (A)(除$ -1 $外各不相同),请输出一个合法的 (P)。
题解
我们令(b_i)表示谁选了(i),拼命观察可知
性质一:(p_{b_i}<p_i)
性质二:(forall jin[1,a_i-1]),如果(j eq i)且(b_j>i),那么(p_j<p_i)
利用这两条性质建拓扑图跑一遍,性质一递归遍历,性质二线段树维护一下
然后分配编号就好了(详见代码)
注意
这里用到了(dfs)写法的拓扑排序,先记录拓扑序大的点(即(p_i)小的点)
code
//需要注意的点:大小关系对应的是要求的数组
#include<bits/stdc++.h>
using namespace std;
const int N=505000;
int n,a[N],b[N];
//a[i]表示i标记了谁,b[i]=a^(-1)(i)表示i被谁标记
#define pr pair<int,int>
#define mk make_pair
#define fi first
#define se second
pr t[N<<2];
#define lson p<<1
#define rson p<<1|1
void build(int p,int l,int r){
if(l==r){
t[p]=mk(b[l],l);
return;
}
int mid=(l+r)>>1;
build(lson,l,mid),build(rson,mid+1,r);
t[p]=max(t[lson],t[rson]);//pair自带比较函数
}
void update(int p,int l,int r,int pos){
if(l==r){
t[p]=mk(0,l);
return;
}
int mid=(l+r)>>1;
if(pos<=mid)update(lson,l,mid,pos);
else update(rson,mid+1,r,pos);
t[p]=max(t[lson],t[rson]);
}
pr query(int p,int l,int r,int L,int R){
if(L<=l&&r<=R)return t[p];
int mid=(l+r)>>1;
if(R<=mid)return query(lson,l,mid,L,R);
else if(L>mid)return query(rson,mid+1,r,L,R);
else return max(query(lson,l,mid,L,R),query(rson,mid+1,r,L,R));
}
int tot,l[N],p[N];//跑反图
bool vis[N];
void dfs(int u){
vis[u]=1;
update(1,1,n,u);//删除拓扑图上这个点
if(b[u]!=n+1&&!vis[b[u]])dfs(b[u]);//p[u]>p[b[u]]
if(a[u]>1){
while(true){
pr now=query(1,1,n,1,a[u]-1);
if(now.fi>u&&!vis[now.se])dfs(now.se);//取max后比较
else break;
//从大往小走
}
}
l[++tot]=u;//注意p[i]最小的在第一个
}//O(nlog(n))
int main(){
// freopen("t.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
if(a[i]!=-1)b[a[i]]=i;
else a[i]=n+1;
}
for(int i=1;i<=n;++i)
if(!b[i])b[i]=n+1;//这样定义方便线段树操作
build(1,1,n);
for(int i=1;i<=n;++i)
if(!vis[i])dfs(i);
tot=0;
for(int i=1;i<=n;++i)p[l[i]]=++tot;
for(int i=1;i<=n;++i)printf("%d ",p[i]);
return 0;
}