Atcoder Grand Contest 031-D: A Sequence of Permutations
题意:给出两个排列p,q其中,a1=p,a2=q,an=f(an-2,an-1)。f(p,q)表示一个第pi个位置等于qi的新排列。
预备知识:关于解这道题,首先要了解离散数学中关于置换群的一些知识,我是在这篇文章中学的:https://wenku.baidu.com/view/72de788aaeaad1f346933f74.html。
但是这篇文章缺少很多知识点,以下是我通过代码实验总结出来的一些关于置换群的性质(假定有两个置换群p,q):
p指向q的一个新置换=q inv§
inv[inv[p]]=p
inv[q inv[p]]=p inv[q]
p inv[p]=(1)
另外附上关于置换的乘法是怎么实现的:https://mp.****.net/mdeditor/88660284#
PS:如果不了解上面这些性质,对于题解的公式变化是看不懂的,其中inv表示置换的求逆。
题解:将p,q看成是两个置换,f(p,q)实际上是由p指向q的一个新置换,因此由f(p,q)=q inv[p]。然后就是推导公式了。这里放上一张官方正解的图:
经过推导后,就会发现在第6项之后有周期性的规律,设A=qp−1q−1p,则inv[A]=p−1qpq−1,然后当n>6的时候,有
an=Aa(n-6)inv[A]=AAa(n-12)inv[A]inv[A]=AAAa(n-18)inv[A]inv[A]inv[A]=…
因此最后就可以推导出如果n>6,an=A-((n-1)/6)a((n-1)%6+1)inv[A-((n-1)/6)],其中-表示次方,因为****打某个符号文档格式就出问题,所以就粗略的用这个表示次方吧。
当n<=6的时候就根据推导出的公式算就行了。最后还有一点是置换A的次方是可以用快速幂计算的,因此这个题就可以根据推导得到的公式快速幂得出答案。
代码:
#include<bits/stdc++.h>
#define vi vector<int>
using namespace std;
vi p,q,a[7];
int n,k,x;
vi mul(vi x,vi y)
{
vi z;
for(int i=0;i<x.size();i++)
z.push_back(x[y[i]]);
return z;
}
vi inv(vi x)
{
vi y(n);
for(int i=0;i<x.size();i++)
y[x[i]]=i;
return y;
}
vi qpow(vi x,int y)
{
vi ans(n);
for(int i=0;i<n;i++) ans[i]=i;
while(y)
{
if(y&1) ans=mul(ans,x);
x=mul(x,x);
y>>=1;
}
return ans;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++)
{
scanf("%d",&x);
p.push_back(x-1);
}
for(int i=0;i<n;i++)
{
scanf("%d",&x);
q.push_back(x-1);
}
a[1]=p;a[2]=q;
for(int i=3;i<=6;i++)
a[i]=mul(a[i-1],inv(a[i-2]));
if(k<=6)
{
for(int i=0;i<n;i++)
printf(i==n-1?"%d\n":"%d ",a[k][i]+1);
return 0;
}
vi A=mul(mul(q,inv(p)),mul(inv(q),p));
A=qpow(A,(k-1)/6);
vi ans=mul(mul(A,a[(k-1)%6+1]),inv(A));
for(int i=0;i<n;i++)
printf(i==n-1?"%d\n":"%d ",ans[i]+1);
}