数论 + 高精度(思维题) - Bogo Sort - 2020牛客暑期多校训练营(第五场)
题意:
给 定 一 个 长 度 为 n 的 置 换 ( p 1 , p 2 , . . . , p n ) , 求 有 多 少 个 排 列 可 通 过 这 个 置 换 变 成 顺 序 。 给定一个长度为n的置换(p_1,p_2,...,p_n),求有多少个排列可通过这个置换变成顺序。 给定一个长度为n的置换(p1,p2,...,pn),求有多少个排列可通过这个置换变成顺序。
示例1
输入
5
1 2 3 4 5
输出
1
示例2
输入
6
2 3 4 5 6 1
输出
6
数据范围:
1 ≤ N ≤ 1 0 5 1≤N≤10^5 1≤N≤105
注: 答 案 对 1 0 N 取 模 。 答案对10^N取模。 答案对10N取模。
分析:
题 意 即 , 每 次 将 序 列 中 的 第 p i 项 移 动 到 第 i 项 , 求 多 少 个 序 列 能 够 在 给 定 置 换 序 列 p i 的 变 换 下 有 序 。 题意即,每次将序列中的第p_i项移动到第i项,求多少个序列能够在给定置换序列p_i的变换下有序。 题意即,每次将序列中的第pi项移动到第i项,求多少个序列能够在给定置换序列pi的变换下有序。
首 先 可 以 确 定 的 是 , 任 意 给 定 的 置 换 , 至 少 有 一 解 ( 有 序 序 列 就 是 一 组 解 ) 。 首先可以确定的是,任意给定的置换,至少有一解(有序序列就是一组解)。 首先可以确定的是,任意给定的置换,至少有一解(有序序列就是一组解)。
接 着 , 如 何 计 算 解 的 数 量 ? 接着,如何计算解的数量? 接着,如何计算解的数量?
我
们
不
妨
直
接
从
有
序
序
列
进
入
变
换
,
我们不妨直接从有序序列进入变换,
我们不妨直接从有序序列进入变换,
我 们 在 p i 与 i 之 间 连 接 一 条 边 , 我们在p_i与i之间连接一条边, 我们在pi与i之间连接一条边,
那 么 每 进 行 一 次 置 换 , 环 上 的 数 字 就 会 依 次 旋 转 一 个 位 置 , 那么每进行一次置换,环上的数字就会依次旋转一个位置, 那么每进行一次置换,环上的数字就会依次旋转一个位置,
当 每 个 环 上 的 数 字 旋 转 一 周 时 , 环 上 的 数 字 就 回 到 了 初 始 有 序 的 状 态 , 当每个环上的数字旋转一周时,环上的数字就回到了初始有序的状态, 当每个环上的数字旋转一周时,环上的数字就回到了初始有序的状态,
而 当 整 个 序 列 所 有 环 上 的 数 都 在 同 一 时 刻 回 到 初 始 有 序 状 态 时 , 整 个 序 列 再 次 回 到 有 序 状 态 , 而当整个序列所有环上的数都在同一时刻回到初始有序状态时,整个序列再次回到有序状态, 而当整个序列所有环上的数都在同一时刻回到初始有序状态时,整个序列再次回到有序状态,
那 么 所 有 的 中 间 状 态 , 就 是 满 足 条 件 的 所 有 序 列 , 那么所有的中间状态,就是满足条件的所有序列, 那么所有的中间状态,就是满足条件的所有序列,
这 些 序 列 的 数 量 应 当 为 所 有 环 的 周 期 的 最 小 公 倍 数 。 这些序列的数量应当为所有环的周期的最小公倍数。 这些序列的数量应当为所有环的周期的最小公倍数。
因 此 , 本 题 的 任 务 即 求 所 有 环 的 长 度 , 再 对 它 们 求 最 小 公 倍 数 。 因此,本题的任务即求所有环的长度,再对它们求最小公倍数。 因此,本题的任务即求所有环的长度,再对它们求最小公倍数。
由 于 答 案 可 能 较 大 , 我 们 要 计 算 高 精 度 。 由于答案可能较大,我们要计算高精度。 由于答案可能较大,我们要计算高精度。
L C M ( a , b ) = a × b G C D ( a , b ) , 计 算 最 大 公 约 数 时 , 可 以 做 如 下 转 化 : LCM(a,b)=\frac{a×b}{GCD(a,b)},计算最大公约数时,可以做如下转化: LCM(a,b)=GCD(a,b)a×b,计算最大公约数时,可以做如下转化:
要 计 算 第 i 个 环 与 前 i 个 环 的 最 小 公 倍 数 , 设 前 i − 1 个 环 的 最 小 公 倍 数 为 C , 第 i 个 环 的 周 期 为 c n t i , 要计算第i个环与前i个环的最小公倍数,设前i-1个环的最小公倍数为C,第i个环的周期为cnt_i, 要计算第i个环与前i个环的最小公倍数,设前i−1个环的最小公倍数为C,第i个环的周期为cnti,
计 算 C 和 c n t i 的 最 大 公 约 数 , 根 据 辗 转 相 除 法 , 等 价 于 计 算 C % c n t i 和 c n t i 的 最 大 公 约 数 。 计算C和cnt_i的最大公约数,根据辗转相除法,等价于计算C\%cnt_i和cnt_i的最大公约数。 计算C和cnti的最大公约数,根据辗转相除法,等价于计算C%cnti和cnti的最大公约数。
由 于 C 是 由 数 组 存 储 的 , C = ∑ i = 1 l e n C i × 1 0 i − 1 , 故 C % c n t i = ∑ i = 1 l e n ( C i × 1 0 i − 1 % c n t i ) , 由于C是由数组存储的,C=\sum_{i=1}^{len}C_i×10^{i-1},故C\%cnt_i=\sum_{i=1}^{len}(C_i×10^{i-1}\%cnt_i), 由于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;
}