Aizu2970 Permutation Sort

题目大意

给你两个 \(n\) 个整数的排列,第一个排列表示原排列,第二个排列表示第 \(i\) 个数可以和 \(i\) 变成第 \(g_i\) 个数,问,最少对所有数进行几次操作可以使原排列变为有序的排列。

题解

首先,我们可以利用第二个排列建图,易得每一个点只有一个出度,一个入度,所以这幅图只由简单环和自环组成。

我们还可以发现,在环上跑大于环的长度的距离等同于跑两点之间的直线距离,也就是说如果环的长度为 \(cnt_i\) ,两点之间的直线距离为 \(x_i\) ,我们要求的距离为 \(d\) ,那么 $d\equiv x_i(mod~cnt_i) $ 。根据每一个 \(i\) ,我们都可以列出这么一个方程,于是问题就转变为求 \(n\) 个同余方程的最小公共解,使用扩展中国剩余定理即可。

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=205;
int n;
int a[N],to[N];
int e[N][N];
int tag[N],cnt[N];
void dfs(int p,int nam)
{
    tag[p]=nam;
    cnt[nam]++;
    if(!tag[to[p]])
    dfs(to[p],nam);
    return ;
}
int gcd(int a,int b)
{
    if(b==0)
    return a;
    return gcd(b,a%b);
}
void exgcd(int a,int b,int &x,int &y)
{
    if(b==0)
    {
        x=1,y=0;
        return ;
    }
    exgcd(b,a%b,x,y);
    int tmp=x;
    x=y;
    y=tmp-a/b*y;
}
signed main()
{
    cin>>n;
    for(int i=1;i<=n;++i)
    cin>>a[i];
    for(int i=1;i<=n;++i)
    for(int j=1;j<=n;++j)
    e[i][j]=1e18+5;
    for(int i=1;i<=n;++i)
    {
        cin>>to[i];
        e[i][to[i]]=1;
    }
    for(int i=1;i<=n;++i)
    e[i][i]=0;
    for(int k=1;k<=n;++k)
    {
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<=n;++j)
            e[i][j]=min(e[i][j],e[i][k]+e[k][j]);
        }
    }
    for(int i=1;i<=n;++i)
    {
        if(e[a[i]][i]>=1e18+5)
        {
            printf("-1\n");
            return 0;
        }
    }
    for(int i=1;i<=n;++i)
    {
        if(!tag[i])
        dfs(i,i);
    }
    int M=cnt[tag[1]],ans=e[a[1]][1];
    for(int i=2;i<=n;++i)
    {
        int x,y,tmp=gcd(M,cnt[tag[i]]),now=((e[a[i]][i]-ans)%cnt[tag[i]]+cnt[tag[i]])%cnt[tag[i]];
        if(now%tmp)
        {
            printf("-1\n");
            return 0;
        }
        exgcd(M,cnt[tag[i]],x,y);
        x*=now/tmp;
        ans+=x*M;
        M*=cnt[tag[i]]/tmp;
        ans=(ans%M+M)%M;
    }
    printf("%lld\n",ans);
    return 0;
}
posted @ 2020-07-27 20:56  Point_King  阅读(141)  评论(0编辑  收藏  举报