这道F题说句实在的,题意能把人看蒙了,看懂了倒是挺简单的,反正我一开始把题意看难了。
题目
题目大意
存在一个长度为 n n n的排列 a a a,然后给出 n − 1 n-1 n−1个子排列,其中每个子排列的元素为 [ a l , a l + 1 , . . . , a r ] [a_l,a_{l+1},...,a_r] [al,al+1,...,ar],其中 r r r是由 2 − n 2-n 2−n里面的数字,每一个数字必将出现一次。求原来的排列。
思想
看到这个每一个数字必将出现一次,就可能想到一个问题了,如果我把
r
r
r前面所有的数字全部确定了,那么第
r
r
r个数字也应该确定了!
证明:假设当前确定了
1
1
1到
r
−
1
r-1
r−1的数字,那么可以发现如果把确定的数字在子排列中标记的话,那么将会出现一段子排列刚好只剩下一个数字没有标记,这就是第r个数字,同时还可以发现,这样的子排列只有一个,因为如果有两个的话,那么第r个数字就有两个,可是只有一个位置,很明显冲突了,可用作剪枝。注意这里只是一个必要条件,它不能充分保证得出的排列一定成立,但是能保证原排列一定符合这个性质,所以我们还需要判断是否符合条件。其时间复杂度为
n
3
∗
l
o
g
2
n
n^3*log_2 n
n3∗log2n
实现
想挺容易的,做起来我真的没想到,
s
e
t
set
set这么骚,要不是学了一点面向对象,我差点理解不了。
每一个子序列都可以放到一个集合里面,每一次标记都是把这个数字从集合里面删除。判断的时候直接看集合是否相同。
介绍几个骚操作:以
s
e
t
<
i
n
t
>
d
set<int> d
set<int>d为例
*d.begin()
为遍历setset<set<int> > d(q.begin(),q.end())
q在这里为vector<set<int> > q
,这个语句的作用是将q这个数组的所有元素存到d这个集合里面。
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <vector>
#include<set>
using namespace std;
const int N = 2e5+5;
typedef long long ll;
#define rep(i,a,b) for(i=(a);i<=b;i++)
#define pt(a) printf("%d\n",(a))
int main()
{
int n,m,i,j,k,t=0;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
vector<set<int> > q;
vector<int> ans;
rep(i,1,n-1){
scanf("%d",&m);
set<int> cur;
rep(j,1,m){
int x;
scanf("%d",&x);
cur.insert(x);
}
q.push_back(cur);
}
rep(i,1,n){
ans.clear();
vector<set<int> > p = q;
int cur = i;
ans.push_back(cur);
rep(j,1,n-1){
int pos = 0 , cnt = 0;
rep(k,0,n-2){
if(p[k].count(cur)) p[k].erase(cur);
if(p[k].size()==1) cnt++,pos = *p[k].begin();//遍历set
}
if(cnt!=1) break;
else cur = pos,ans.push_back(cur);
}
if(j==n){
set<set<int> > d(q.begin(),q.end());//自动把q数组全部放到set里面
bool ok = true;
for(int r=1;r<n;r++){
set<int> f;
f.clear();
bool flag = false;
for(int l=r;l>=0;l--){
f.insert(ans[l]);
//printf("%d ",ans[l]);
if(d.count(f)) {
flag = true;
break;
}
}
// printf("yes%d %d\n",i,flag);
if(!flag) {
ok = false;
break;
}
}
if(ok){
for(int l=0;l<n;l++){
printf("%d ",ans[l]);
}
printf("\n");
break;
}
}
}
}
//system("pause");
return 0;
}