并查集 ACWing 1921. 重新排列奶牛

1921. 重新排列奶牛 - AcWing题库

农夫约翰的 N𝑁 头奶牛排成一排,编号 1∼N1∼𝑁。

它们的排序可以由一个数组 A𝐴 来描述,其中 A(i)𝐴(𝑖) 是位于位置 i𝑖 的奶牛的编号。

约翰希望将它们重新排列为一个不同的顺序。

新顺序用数组 B𝐵 来描述,其中 B(i)𝐵(𝑖) 是位于位置 i𝑖 的奶牛的编号。

假设奶牛开始时的顺序为:

A = 5 1 4 2 3

并假设约翰希望它们重新排列为:

B = 2 5 3 1 4

为了从 A𝐴 顺序重新排列为 B𝐵 顺序,奶牛们进行了许多“循环”移位。

所谓循环移位,是指挑选排列中的若干头奶牛分在一组,组中奶牛进行循环移动位置,即第一头奶牛移动至第二头奶牛的位置,第二头奶牛移动至第三头奶牛的位置,…,最后一头奶牛移动至第一头奶牛的位置。

如上例中,将 5,1,25,1,2 号奶牛分在一组进行循环移位,移动过后,55 号奶牛移动至位置 22,11 号奶牛移动至位置 44,22 号奶牛移动至位置 11;将 4,34,3 号奶牛分在另一组进行循环移位,移动过后,44 号奶牛位于位置 55,33 号奶牛位于位置 33;最终完成重新排列。

每头奶牛都恰好参与一组循环移位,除非其在 A,B𝐴,𝐵 中的位置没有变化。

请计算奶牛们完成重新排列,共需多少组循环移位,最长的一组循环移位的长度是多少。

输入格式

第一行包含整数 N𝑁。

接下来 N𝑁 行包含 A(i)𝐴(𝑖)。

再接下来 N𝑁 行包含 B(i)𝐵(𝑖)。

输出格式

输出循环移位的组数,以及最长的一组循环移位的长度。

如果不存在循环移位,则第二个数输出 −1−1。

数据范围

1≤N≤1001≤𝑁≤100,
1≤A(i),B(i)≤N1≤𝐴(𝑖),𝐵(𝑖)≤𝑁

输入样例:
5
5
1
4
2
3
2
5
3
1
4
输出样例:
2 3

所谓“循环”移位: 

A = 5 1 4 2 3
B = 2 5 3 1 4

由A数组循环位移到B数组:

A数组中1的位置被5占了:5--1,1--2,4--3,2--4,3--5

B数组中:2(1) 5(2) 3(3) 1(4) 4(5)

由A变到B:A中5的位置被2占,1的位置被4占,4的位置被2占,2占了1的位置,这样一组有向图(环)的就为一组循环位移。

这道题的目的就是找出多少个环,并且为每一个环的元素个数最大。

通过维护环(连通块中点的数量),那么用并查集:

 

const int N = 110;

int a[N],b[N],p[N],s[N];
//a,b为输入数组,p为并查集数组,s为每个并查集中点的数量

输入数据

for(int i = 1;i<=n;i++) cin >>a[i];

for(int i = 1;i<=n;i++) 
{
    int x;
    cin >>c;
    b[x] = i;
}

为什么b数组里是b[x] = i ?

因为我们要通过a数组中元素在b中的位置,然后再在a中的位置来判断是a数组的两次位置之间是否由一条有向边,并且我们要对比的是a中的元素是否在正确的位置。

#初始化并查集
for(int i = 1;i<=n;i++)
{

    p[i] = i;
    s[i] = 1;
}

假如由int x = a[i] ,那么我们要找出x在B数组中的位置:b[x],然后再在找出a数组中相应的位置:a[b[x]],这样我们就有了一条有向边,之后我们在找出构成有向边的两个点是不是在同一个集合,

如果不在就合并这两个集合,并且相应的集合位置上的元素数量 +1 

for(int i = 1;i<=n;i++)
{
    int x = a[i],y = a[b[x]];
    if(find(x) != find(y)) #不在同一集合
    s[find(y)] += s[find(x)];
    p[find(x)] = p[find(y)];
}

 

#进行统计
int cnt = 0,mx = 1;
    for(int i = 1;i<=n;i++)
    {
        if(p[i] == i && s[i] > 1)
            cnt++;
        mx = max(mx,s[i]);
    }

#并查集find()函数:
int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x]
}

完整代码:

#include<iostream>

using namespace std;

const int N = 110;
int p[N];
int a[N],b[N],s[N];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}


int main()
{
    int n;
    cin >>n;
    for(int i = 1;i<=n;i++) cin >>a[i];
    for(int i = 1;i<=n;i++) 
    {
        int x;
        cin >>x;
        b[x] = i;
    }
    
    for(int i = 1;i<=n;i++) 
    {
        p[i] = i,s[i] = 1;
    }
    
    for(int i = 1;i<=n;i++)
    {
        int x = a[i],y = a[b[x]];
        if(find(x) != find(y))
        {
            s[find(y)] += s[find(x)];
            p[find(x)] = p[find(y)];
        }
    }
    
    int cnt = 0,mx = 1;
    for(int i = 1;i<=n;i++)
    {
        if(p[i] == i && s[i] > 1)
            cnt++;
        mx = max(mx,s[i]);
    }
    
    if(!cnt) cout <<cnt <<" "<<"-1";
    else 
    cout <<cnt <<" "<<mx;
    return 0;
    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值