CF1343F Restore the Permutation by Sorted Segments

特别感谢机房 suxxsfe 神犇的详细讲解和代码示范!

一道思路很自然的构造题。

题目给出了一个重要信息,即每个序列 \(q\) 和一个 \(r\in [2,n]\) 一一对应,这意味着,只要确定序列 \(p\) 的首项,整个序列就确定了。(请注意区分 \(p\) 和 \(q\)——与原题一致的,\(p\) 代表原始的序列,\(q\) 代表给出的序列片段们。)

为什么?当我们确定了首项,就不难通过对应 \(r=2\) 的片段 \(q\) 确定第二项;确定了第二项,又可以经由 \(r=3\) 对应的片段 \(q\) 确定第三项,以此类推。

一个做法呼之欲出:从 \(1\) 到 \(n\) 枚举首项,将它从所有的片段 \(q\) 中删去。如果合法,当前一定有且只有一个 \(q\) 长度变为 \(1\)。剩下的那个数就是 \(p_2\)。

再将得到的 \(p_2\) 从剩余的片段 \(q\) 里删去,\(p_3\) 也能得到。不断重复这一操作,并把删掉的数字依顺序扔进一个队列。如果 \(p_1\) 正确,我们最终会得到一个填满的答案序列。

如果中途出现了这两种情况,我们认定为不合法,直接 continue 到下一个 \(p_1\):

  • 找不到那个应当被删除的数字;
  • 删完后,同时有两个片段 \(q\) 长度为 \(1\)

这一部分代码如下(笔者用 set 维护片段 \(q\),时间复杂度增长到 \(O(qn^3log_2n)\),可以不用 set 进行 \(O(n^2)\) 维护,不带 log):

cin>>n;
for(int i=1,m,x;i<n;++i)
{
	se[i].clear(),cin>>m;
	while(m--) cin>>x,se[i].insert(x);
}
for(int p1=1;p1<=n;++p1)
{
	for(int i=1;i<n;++i) nw[i]=se[i];
	del(p1),ans[1]=p1,id[p1]=1;//ans[] 为答案序列,id[]为每个数字出现的位置
	//del(x) 函数扫一遍 set,删掉所有 x
	for(int i=2;i<=n;++i)
	{
		int pn=find();//find() 函数查找删除后长度余 1 的片段,取出那个数字
		if(!pn) goto Fail;
		del(pn),ans[i]=pn,id[pn]=i;
	}
	//输出答案
	Fail:;
}

看似做完了,实则还有一步:得到的“答案序列”仍然不一定正确(指 WA on test1)!问题出在哪里?

看这一组样例:

6
3 2 5 6
2 4 6
3 1 3 4
2 1 3
4 1 2 4 6

答案是 3 1 4 6 2 5,而我们的代码输出 1 3 4 6 2 5。这是不合法的,原因是没有“紧接着”删除掉 \(1\) 后面的数字。如何解决?可以再加一个 checker(),对每一个要求再 \(O(n)\) 判断一遍。

具体代码如下:

inline bool chk(){
	for(int i=1,tot;i<n;++i){
		tot=0;
		for(int j=1;j<=n;++j) 
			if(se[i].count(j)) a[++tot]=id[j];
		sort(a+1,a+tot+1);
		for(int j=2;j<=tot;++j)
			if(a[j]!=a[j-1]+1) return false;
	}
	return true;
}

时间复杂度还是 \(O(qn^3log_2n)\),超过 1e9,但是毕竟 CF 神机嘛。

THE END

上一篇:[题解]剑指 Offer 38. 字符串的排列(C++)


下一篇:LeetCode 1920. 基于排列构建数组(Build Array from Permutation)