1. 程式人生 > 實用技巧 >題解 Aizu2970 【Permutation Sort】

題解 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;
}