数论 + 高精度(思维题) - Bogo Sort - 2020牛客暑期多校训练营(第五场)

数论 + 高精度(思维题) - Bogo Sort - 2020牛客暑期多校训练营(第五场)

题意:

n ( p 1 , p 2 , . . . , p n ) 给定一个长度为n的置换(p_1,p_2,...,p_n),求有多少个排列可通过这个置换变成顺序。

示例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 0 N 答案对10^N取模。


分析:

p i i p i 题意即,每次将序列中的第p_i项移动到第i项,求多少个序列能够在给定置换序列p_i的变换下有序。

( ) 首先可以确定的是,任意给定的置换,至少有一解(有序序列就是一组解)。

接着,如何计算解的数量?

在这里插入图片描述
我们不妨直接从有序序列进入变换,

p i i 我们在p_i与i之间连接一条边,

那么每进行一次置换,环上的数字就会依次旋转一个位置,

当每个环上的数字旋转一周时,环上的数字就回到了初始有序的状态,

而当整个序列所有环上的数都在同一时刻回到初始有序状态时,整个序列再次回到有序状态,

那么所有的中间状态,就是满足条件的所有序列,

这些序列的数量应当为所有环的周期的最小公倍数。

因此,本题的任务即求所有环的长度,再对它们求最小公倍数。

由于答案可能较大,我们要计算高精度。

L C M ( a , b ) = a × b G C D ( a , b ) : LCM(a,b)=\frac{a×b}{GCD(a,b)},计算最大公约数时,可以做如下转化:

i i i 1 C i c n t i 要计算第i个环与前i个环的最小公倍数,设前i-1个环的最小公倍数为C,第i个环的周期为cnt_i,

C c n t i C % c n t i c n t i 计算C和cnt_i的最大公约数,根据辗转相除法,等价于计算C\%cnt_i和cnt_i的最大公约数。

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),

这样,我们就能够将求最大公约数转化到整形范围内的计算。

代码:

#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;
}

猜你喜欢

转载自blog.csdn.net/njuptACMcxk/article/details/107602009