DTOJ3084 置换permutation

题目

题目描述

定义一个置换$P$的平方$Q$为对$[1,2,3,\cdots \cdots,n -1,n]$做两次该置换得到的排列,即$Q_i=P_{p_i}$
现在已知一个置换的平方,求该置换

输入格式

第一行一个整数$n$表示排列长度
第二行$n$个整数表示所求置换的平方

输出格式

若有解则输出一行$n$个数表示原置换(输出任意一个),否则输出$-1$

样例

样例输入

1
2
4
2 1 4 3

样例输出

1
3 4 2 1

数据范围与提示

$20\%$的数据:$n\leqslant 10$
$100\%$的数据:$1\leqslant n\leqslant 10^6$

题解

我们先来观察一下置换平方后是什么鬼
假设我们有一个置换:$(2,3,1,5,4)$
它可以被拆解为两个环:$[2,3,1][5,4]$
我们把它平方一下:$(3,1,2,4,5)$
发现它可以拆成三个环:$[3,1,2][4][5]$
我们发现,如果是一个偶环,平方后就拆开了,如果是一个奇环,就还会是一个奇环
得到了这个规律以后,我们就可以处理这道题了
先把平方后的结果拆成环,对于偶环,就两两合并,比如$[1,3,5]$和$[2,4,6]$合并为$[1,2,3,4,5,6]$;对于奇环,通过观察,可以发现,我们需要把它拆成两半之后合并,比如$[1,3,2]$拆成$[2,1][3]$合并为$[2,3,1]$(注意,这里的环指的并不是按照置换中的顺序排的,而是按遍历的顺序)
最后,对于输出$-1$的情况当然只有一个啦,那就是有奇数个偶环
附上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
int n,Size,q[1000010],v[1000010],p[1000010];
vector<int> h[1000010],l[1000010];
int main()
{
freopen("permutation.in","r",stdin);
freopen("permutation.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&q[i]);
for(int i=1;i<=n;i++){
int s=0;
for(int j=i;!v[j];j=q[j]) v[j]=1,s++,h[i].push_back(j);
l[s].push_back(i);
}
for(int i=1;i<=n;i++) if((!(i&1))&&(l[i].size()&1)){printf("-1");return 0;}
memset(v,0,sizeof(v)),Size=l[1].size();
for(int i=0;i<Size;i++) p[l[1][i]]=l[1][i];
for(int i=2;i<=n;i++){
Size=l[i].size();
for(int j=0;j<Size;j++)
if(i&1){
int x=h[l[i][j]][0],y=h[l[i][j]][i/2+1];
while(!v[x]) v[x]=v[y]=1,p[x]=y,x=q[x],p[y]=x,y=q[y];
}
else{
int x=h[l[i][j]][0],y=h[l[i][j+1]][0];
while(!v[x]&&!v[y]) v[x]=v[y]=1,p[x]=y,x=q[x],p[y]=x,y=q[y];
j++;
}
}
for(int i=1;i<=n;i++) printf("%d ",p[i]);
}