Codeforces 612E

来源:互联网 发布:java base64解码图片 编辑:程序博客网 时间:2024/05/29 15:39


E. Square Root of Permutation

A permutation of length n is an array containing each integer from 1 to n exactly once. For example, q = [4, 5, 1, 2, 3] is a permutation. For the permutation q the square of permutation is the permutation p that p[i] = q[q[i]] for each i = 1… n. For example, the square of q = [4, 5, 1, 2, 3] is p = q^2 = [2, 3, 4, 5, 1].

This problem is about the inverse operation: given the permutation p you task is to find such permutation q that q^2 = p. If there are several such q find any of them. 
Input

The first line contains integer n (1 ≤ n ≤ 106) — the number of elements in permutation p.

The second line contains n distinct integers p1, p2, …, pn (1 ≤ pi ≤ n) — the elements of permutation p. 
Output

If there is no permutation q such that q2 = p print the number “-1”.

If the answer exists print it. The only line should contain n different integers qi (1 ≤ qi ≤ n) — the elements of the permutation q. If there are several solutions print any of them. 
Sample test(s) 
Input


2 1 4 3

Output

3 4 2 1

Input


2 1 3 4

Output

-1

Input


2 3 4 5 1

Output

4 5 1 2 3


题意: 
存在一个1-n的排列p。 
现在定义一个1-n的排列q,使得q[i] = p[p[i]]。 
给出q,输出一个合法的p。不存在输出-1。

思路1: 
显然置换群。置换群能分成若干个不相交的环。 
环,首先有个可以自己手动模拟出的结论。 
1.环的元素个数是偶数,经过一次映射后,环会分裂成两个元素大小相等的环。 
2.环的元素个数是奇数,经过一次映射后,环只是移动位置而不会发生分裂。 
那么具体如何分裂?假设当前从1出发,经过两次映射后到3、再到5… 
故容易知道环的元素个数为奇数则不会发生分裂,且相对于原来的顺序是1357…2468.. 
故容易知道环的元素个数为偶数会发生分裂,且相对于原来的顺序1357…、2468…

那么,现在求逆运算的时候,如果遇到奇数环,是可逆的;偶数环,和另外一个相同长度的偶数环合并。 

思路2:

置换的预备知识:
(a1,a2,a3)表示a1->a2,a2->a3,a3->a1;由于映射为一一对应关系,具有可逆性;(求解的依据)
对于一个置换(a1…an)一定可以划分为若干个不相交的循环
如(2,1,4,3) 变成置换之后为(1,2)(3,4) 
特别注意循环乘法之间拆合关系(逆推原始的循环)以及元素位置的改变(得到答案): ans[位置] = val;

对于循环里面的元素个数为奇数时,(a[1],a[2],a[3])(a[1],a[2],a[3]) = (a[1],a[3],a[2]);即 a[1]->a[2]->a[3] ==>a[1]->a[3];同理a[2]->a[3]->a[1] ==>a[2]->a[1]; a[3] ->a[2];扩展到size = 2k+1个数的循环相乘呢?很容易知道当下标从0开始时;每个数的位置变成了2*i%size;同时告诉我们,循环的大小为奇数时,可以由自己生成,所以不用去管奇数的是否配对;

为偶数时:如(a1,a2,a3,a4)^2 = (a1,a3)(a2,a4);这就告诉我们,当p中分解出来的一个循环的大小为偶数时,循环只能由一个2*n的循环分裂得到(那我们在你想得到答案时就需要两个2k大小的循环合并成一个大小为4k的循环),在这时判断是否无解;偶数大小的循环平方时个数二分;而奇数只是把同奇偶(以下标看奇偶)的合并在一起了;

在编码时,开始就是用了循环分解算法,分解成cnt个循环节;可能会怀疑为什么这就是在一个循环里面呢?依上面的置换乘积可以看出循环大小为奇数时,只是调换了元素的对应关系,但是并没有改变ai的值,所以我们只需按照这个调换关系,逆着推回去即可;(奇偶调换两次就等于没调换~~),如果是偶数,那么分裂后还在一个循环中的元素,之前一定在同一个循环中,所以只需能配对就可以得到配对的两个的父循环~~(逆推的原理,也是理解的关键)

四个结论:

结论一: 一个长度为 l 的循环 T,l 是 k 的倍数,则 T^k 是 k 个循环的乘积,每个

循环分别是循环 T 中下标 i mod k=0,1,2… 的元素按顺序的连接。 
结论二:一个长度为 l 的循环 T,gcd(l,k)=1,则 T^k 是一个循环,与循环 T 不一

相同。

结论三:一个长度为 l 的循环 T,T^k 是 gcd(l,k)个循环的乘积,每个循环分别是循

 T 中下标 i mod gcd(l,k)=0,1,2… 的元素的连接。 

单个循环置换幂运算:

1、循环长度与指数互质时的整幂运算

设T=a,T^k=b,且gcd(L,k)=1,则b[i]=a[(k+1)*i%L]。由此可以构造出T^k(如L=10,k=3)



2、循环长度与指数互质的分数幂运算(开方)

构造方法与上述过程类似但互逆,不过就是目标循环每次指针向后移k位,源循环每次向后移1位罢了。

此算法可用于解决poj1721

3、循环长度与指数不互质时,单个循环是不能开方的。



多个循环的幂运算

1.整数幂运算:(p1 p2 p3...pn)^k=(p1)^k (p2)^k...(pn)^k不同循环节独立运算,整数次幂运算一定产生分裂效果。

例:poj3281 Leonardo's Notebook判断一个置换是否是一个置换平方后的结果,对于给出的置换,长度为偶数的循环节一定是由分裂产生的,因此必须成对出现,对于长度为奇数的循环节可能由于分裂产生也可能在原置换中独立存在,因此只要考虑偶置换是否成对。

2、分数幂运算:在开方运算中,我们可以将k份相同长度l的循环依次交错地合并,作为一次开方的过程。因为这样一个长度k*L的循环,作k次幂运算,势必会分裂成这样的k份每份长度L的循环,所以这个做法是正确的。

     将k个循环分别进行开方,得到的仍然是k个小循环,结果也是正确的。


定理:如果我们选择m个相同长度L的循环合并而作为一次运算过程的话,只要保证长度m*L的循环的k次方会把自己分裂为m份,就可以保证这个做法是正确的了。即:gcd(m*L,k)=m。

m是k的因数,并且gcd(L,k/m)=1。显然,这个式子的充分条件是m是gcd(L,k)的倍数

算法:较整幂运算来说,分数幂运算(开方)就比较复杂了,需要分好几种情况,解也不是唯一的。整理提供一个能求出一个正确解的O(n)算法

1. m=gcd(L,k)
2. 选择n*m份长度为L的循环交错合并,n为正整数,且gcd(n*m,k/m)=1(保证第三步可行)
3. 将大循环开k/m次方


代码:

#include<bits/stdc++.h>using namespace std;const int MAXN = 1e6+10;int B[MAXN],vis[MAXN],ans[MAXN],id[MAXN];vector<int> v[MAXN];#define pb push_backbool cmp(const vector<int> &a,const vector<int> &b){    return a.size() < b.size();}int main(){    int i,n,cnt = 0,tot = 0;    cin>>n;    for(i = 1;i <= n;i++)        scanf("%d",B+i);    for(i = 1;i <= n;i++)if(vis[i] != 1)    {        int tmp = i;        ++cnt;        do        {            vis[tmp] = 1;            v[cnt].pb(tmp);            tmp = B[tmp];        }while(tmp != i);    }    sort(v+1,v+1+cnt,cmp);    for(i = 1;i <= cnt;i++)    {        int sz = v[i].size();        if(sz & 1)        {            for(int k = 0;k < sz;k++)                id[k*2%sz] = v[i][k];            for(int k = 0;k < sz;k++)                ans[id[k]] = id[(k+1)%sz];        }        else if(sz == v[i+1].size())        {            for(int k = 0;k < sz;k++)            {                ans[v[i][k]] = v[i+1][k];                ans[v[i+1][k]] = v[i][(k+1)%sz];            }            i++;        }        else        {            return puts("-1"),0;        }    }    for(i = 1;i <= n;i++)    {        printf("%d ",ans[i]);    }}


0 0
原创粉丝点击