CF1158C Permutation recovery 题解

前置

第一次独立做出来 *2100 的题,肯定是 cf 评分评高了。

题意简述

有一个排列 p p p,定义 n x t i nxt_i nxti p i p_i pi 右侧第一个大于 p i p_i pi 的位置。若不存在这样的位置则 n x t i = n + 1 nxt_i=n+1 nxti=n+1

现在排列 p p p 和部分 n x t i nxt_i nxti 丢失了。你需要构造出一个排列 p p p,满足给出的 n x t i nxt_i nxti 关系。 n x t i = − 1 nxt_i=-1 nxti=1 表示 n x t i nxt_i nxti 丢失(即你可以忽略这个 n x t i nxt_i nxti 关系)。

若无解,输出 -1。若有多组解,输出任意一组即可。

解题思路

先来考虑什么时候无解。对于一个 i i i,如果存在一个位置 j j j,满足 i < j < n x t i i<j<nxt_i i<j<nxti n x t j > n x t i nxt_j>nxt_i nxtj>nxti,则无解。理解一下,就是说给出的 n x t i nxt_i nxti 会推出大小关系冲突。

再来考虑构造一个合法的解。我们可以将 n x t i = − 1 nxt_i=-1 nxti=1 n x t i nxt_i nxti 全部替换成 i + 1 i+1 i+1。这样的替换一定不会导致有解的序列变成无解。然后我们开一个堆存储当前还未选择的数有哪些,并确立 i i i n x t i nxt_i nxti 的大小关系,从堆中每次选最大的数填就可以了。可以看代码理解一下。

代码示例

#include<bits/stdc++.h>
using namespace std;
#define int long long
int t,n,nxt[500010],st[50][500010],ans[500010];
multiset<int> s;
vector<int> G[500010];
void init(){
	s.clear();
	for(int i=1;i<=n;i++) G[i].clear();
	for(int i=1;i<=n;i++) s.insert(i);
	for(int i=1;i<=n;i++) st[0][i]=nxt[i];
	for(int i=1;i<=__lg(n);i++){
		for(int j=1;j+(1<<i)-1<=n;j++) st[i][j]=max(st[i-1][j],st[i-1][j+(1<<(i-1))]);
	}
}
int query(int l,int r){
	int len=__lg(r-l+1);
	return max(st[len][l],st[len][r-(1<<len)+1]);
}
void solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>nxt[i];
	for(int i=1;i<=n;i++) if(nxt[i]==-1) nxt[i]=i+1;
	init();
	for(int i=1;i<=n;i++){
		if(nxt[i]==i+1) continue;
		if(query(i+1,nxt[i]-1)>nxt[i]){
			cout<<-1<<endl;
			return;
		}
	}
    //st表判断无解情况
	for(int i=1;i<=n;i++) G[nxt[i]].push_back(i);
    //确立大小关系
	for(int i=1;i<=n;i++){
		if(nxt[i]==n+1){
			ans[i]=*s.rbegin();
			s.erase(s.find(*s.rbegin()));
		}
	}
    //初始化 nxt_i=n+1 的位置
	for(int i=n;i>=1;i--){
		for(int v:G[i]){
			ans[v]=*s.rbegin();
			s.erase(s.find(*s.rbegin()));	
		}
        //每次找最大的未使用的数填入即可
	}
	for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
	cout<<endl;
	return;
}
signed main(){
	cin>>t;
	while(t--) solve();
	return 0;
}
  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值