数论 + 高精度(思维题) - Bogo Sort - 2020牛客暑期多校训练营(第五场)
题意:
给定一个长度为n的置换(p1,p2,...,pn),求有多少个排列可通过这个置换变成顺序。
示例1
输入
5
1 2 3 4 5
输出
1
示例2
输入
6
2 3 4 5 6 1
输出
6
数据范围:
1≤N≤105
注:
答案对10N取模。
分析:
题意即,每次将序列中的第pi项移动到第i项,求多少个序列能够在给定置换序列pi的变换下有序。
首先可以确定的是,任意给定的置换,至少有一解(有序序列就是一组解)。
接着,如何计算解的数量?
我们不妨直接从有序序列进入变换,
我们在pi与i之间连接一条边,
那么每进行一次置换,环上的数字就会依次旋转一个位置,
当每个环上的数字旋转一周时,环上的数字就回到了初始有序的状态,
而当整个序列所有环上的数都在同一时刻回到初始有序状态时,整个序列再次回到有序状态,
那么所有的中间状态,就是满足条件的所有序列,
这些序列的数量应当为所有环的周期的最小公倍数。
因此,本题的任务即求所有环的长度,再对它们求最小公倍数。
由于答案可能较大,我们要计算高精度。
LCM(a,b)=GCD(a,b)a×b,计算最大公约数时,可以做如下转化:
要计算第i个环与前i个环的最小公倍数,设前i−1个环的最小公倍数为C,第i个环的周期为cnti,
计算C和cnti的最大公约数,根据辗转相除法,等价于计算C%cnti和cnti的最大公约数。
由于C是由数组存储的,C=∑i=1lenCi×10i−1,故C%cnti=∑i=1len(Ci×10i−1%cnti),
这样,我们就能够将求最大公约数转化到整形范围内的计算。
代码:
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=1e5+10;
int n,p[N];
int cnt[N],idx;
bool vis[N];
int C[N],len;
int gcd(int a,int b)
{
return b ? gcd(b,a%b) : a;
}
int gcd(int x)
{
int t=0;
for(int i=len;i;i--)
{
t=t*10+C[i];
t%=x;
}
return gcd(t,x);
}
void mul(int k)
{
int n=len+5;
for(int i=1;i<=n;i++) C[i]*=k;
for(int i=1;i<=n;i++)
{
C[i+1]+=C[i]/10;
C[i]%=10;
}
while(C[n]==0) n--;
len=n;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
for(int i=1;i<=n;i++)
if(!vis[i])
{
idx++;
int j=i;
while(!vis[j])
{
vis[j]=true;
cnt[idx]++;
j=p[j];
}
}
len=1,C[1]=1;
for(int i=1;i<=idx;i++)
{
cnt[i]/=gcd(cnt[i]);
mul(cnt[i]);
}
for(int i=len;i;i--) printf("%d",C[i]);
return 0;
}