Codeforces Round #559 (Div. 2) : E - Permutation recovery (思维,dfs)

27 篇文章 0 订阅

题目大一:有一个n个元素的排列,但这个排列只给出每一个元素的next[i], next[i]表示的是 第一个下标大于i且值大于i的元素的下标,如果后面不存在比它大的数,则next[i] = n + 1,现在这个序列丢失了一部分,丢失的值的next[i] 定义为 -1 ,问你能不能复原这个序列,使得它满足现在还保留的的next[i],并且合法。

分析:明显是一道构造算法,常规思路:假设没有-1的情况,先思考所有next[i] = n + 1的位置,可以从最前面到后面一路放cur,每放一个cur - 1。这样就构造出一部分合法序列了,接下来怎么放呢?

在纸上画一个序列就可以发现,接下来能放的数字是后面没有比它大的数字,也就是类似next[i] = n + 1的情况,但它的next[i] 不等于n + 1,但此时它的next[i] 一定被我们放过,因此可以每次放那些后面没有比它大的数字且还每放过的数字就行了,如果有多个这样的数字,从前往后放。

n + 1是一个特殊情况,可以转化为一般情况:先放n + 1,再放n…,这样所有数字处理方法都是一样的了。大致过程为:如果我们定义 一个数字已经确认,则它的next[i] = 0,先放next[i] = 0的数字,放完之后有一些数字肯定又可以放了,因为它的next[i] 放过了,所以要修改它的next[i] = 0。

直接描述不太清楚,这个过程很像拓扑排序。
考虑建图:反向建边,从next[i] 建一条边到 i,从入度为0的点开始放,每放一个就删掉对应点的入度,可以发现这样构造是对的,但是要怎么从前往后放呢?存图的时候排序就行了,事实上按顺序读入建边图直接是有序的。

题目又说可能无解:思考什么情况下无解?就是边相交的情况,即i < j < next[i] < next[j] 这种情况,因为如果 next[j] > next[i] > j,那么a[j] 一定 > a[next[i]] ,next[i] 就可以等于 j 而不是大于 j 。

所以要先判一下是否存在边相交的情况,可以o(n)判断(这里代码卡了好久)

图其实比较特殊,因为是在序列上,每一个i 都有一个next[i] (除了-1),并且边不相交且没有环和自环,是一个森林,根本不需要拓扑排序,直接dfs就可以了,每次访问一个结点时,按子节点编号从小到大dfs,这和前面的过程是一样的,至于next[i] = -1的情况,其实就是新的根节点,再沿着这棵树跑dfs即可。

(此类图模型实际是我以前做过的图模型,多思考一下能想起来就不会画这么多时间想解法上了)

注意多颗森林一定要先跑根节点为n + 1的那颗树,next[i] = -1的点是影响最小的点,但是如果先跑next[i] = -1的点可能会影响n + 1的那棵树

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 10;
int n,t,cur;
int a[maxn],ind[maxn],ans[maxn];
vector<int> g[maxn];
int sta[maxn],top;
void tpsort(int t) {
	ans[t] = cur--;
	for(int i = 0; i < g[t].size(); i++) {
		int v = g[t][i];
		ind[v]--;
		if(!ind[v]) tpsort(v);
	}
}
int main() {
	scanf("%d",&t);
	while(t--) {
		scanf("%d",&n);
		for(int i = 1; i <= n + 1; i++) ind[i] = 0, ans[i] = 0;
		for(int i = 1; i <= n; i++) {
			scanf("%d",&a[i]);
			if(a[i] == -1) continue;
			g[a[i]].push_back(i);
			ind[i]++;
		}
		top = 0;
		int r = 1e6;
		bool flag = false;
		sta[0] = 1e6;
		for(int i = 1; i <= n; i++) {
			while(i == r) {
				r = sta[top--];
			}
			if(a[i] > r) {
				flag = true;
				break;
			}
			if(a[i] < r && a[i] != -1) {
				sta[++top] = r;
				r = a[i];
			}
		} 
		cur = n + 1;
		a[n + 1] = -1,ans[n + 1] = 0;
		for(int i = n + 1; i >= 1; i--)
			if(a[i] == -1 && ans[i] == 0)
				tpsort(i);
		if(flag) printf("-1");
		else {
			for(int i = 1; i <= n; i++) {
				if(i - 1) printf(" ");
				printf("%d",ans[i]);
			}
		}
		for(int i = 1; i <= n + 1; i++) g[i].clear();
		puts("");
	}
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值