特别感谢机房 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 神机嘛。