重新排列奶牛

重新排列奶牛

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

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

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

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

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

A = 5 1 4 2 3

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

B = 2 5 3 1 4

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

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

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

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

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

输入格式

第一行包含整数 N

接下来 N 行包含 A(i)

再接下来 N 行包含 B(i)

输出格式

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

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

数据范围

1N100,
1A(i),B(i)N

输入样例:

复制代码
5
5
1
4
2
3
2
5
3
1
4
复制代码

输出样例:

2 3

 

解题思路

  如果第i个数a[i]在序列b中的第j个位置,并且如果有ij,那么我们就将a[i]a[j]连一条有向边,方向从a[i]a[j],表示a[i]要与a[j]进行交换,这样a[i]才能够在序列bj的这个位置。

  以样例为例:

  因此问题就变成了经过上面的操作后,一共有几个环(环中结点的数量要大于1)。

  然后题目表述的不是很清楚,当时一直想不明白如果遇到a=[1,3,2], b=[3,2,1]这种情况应该怎么交换。按照我一开始的理解,所谓的循环交换是指按照环的顺序进行移位,因此对于序列1 3 2无论怎么移位,都只有2 1 3, 3 2 1, 1 3 23种情况,不可能会出现3 2 1。然而事实是,这里的循环移位是指,选择其中一头牛,然后放到其他牛的地方。然后再把刚才被换出的牛放到其他没被换过的牛的地方,以此类推,直到最后一头牛,就放到空的位置上(这个空的位置就是一开始进行交换的牛的那个位置)。

  以上面的例子为例,一开始先选择3号牛,把3放到第1个位置,置换出1号牛,然后再把1号牛放到第3个位置,置换出2号牛,最后把2号牛放到空的位置。

  根据这些环的性质,我们可以发现这是一个环图的模型。因为如果一个点要放到另外一个点上,那么这个点就跟另外一个点连一条边,这样的话每一个点有且只有一条出边,和有且只有一条入边。

  环图是指:

  1. 如果一个图是有向图,每个点有且只有一条出边,有且只有一条入边,那么这个图是环图。
  2. 如果一个图是无向图,每个点的的度数为2,那么这个图是环图。

  环图一定是由若干个环构成的。我们可以从任意一个结点出发,那么一定可以从这个点走到另外一个点,继续一直走下去,那么最后一定会走到起点。这是因为,再走的过程中,如果走不到起点就会一直走,由于我们点的数量是有限的,所以最终一定会走到之前走过的某一个点,如果这点不是起点,那么这个点的入度就为2了,与每个点只有1个入度产生矛盾,因此最终一定会走到起点。而且也不会走到其他的环上,一样会有入度为2的矛盾。

  这是一个有向图,但环与环之间是不相交的。要求环的数量,等价于求连通块的数量(一个图是有向图不等价于连通块,只有是环图才等价于连通块)。维护连通块就可以用并查集了。

  AC代码如下:

复制代码
 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 const int N = 110;
 6 
 7 int a[N], pos[N];
 8 int fa[N], cnt[N];
 9 
10 int find(int x) {
11     return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
12 }
13 
14 int main() {
15     int n;
16     scanf("%d", &n);
17     for (int i = 1; i <= n; i++) {
18         scanf("%d", a + i);
19     }
20     for (int i = 1; i <= n; i++) {
21         int val;
22         scanf("%d", &val);
23         pos[val] = i;   // pos[i]是把数字i映射到在数组b中的下标,这里存的是数字val在b中的位置
24     }
25     
26     for (int i = 1; i <= n; i++) {
27         fa[i] = i;
28         cnt[i] = 1;
29     }
30     
31     for (int i = 1; i <= n; i++) {
32         int x = a[i], y = a[pos[a[i]]];
33         if (find(x) != find(y)) {
34             cnt[find(x)] += cnt[find(y)];
35             fa[y] = find(x);
36         }
37     }
38     
39     int ret = 0, maxv = 0;
40     for (int i = 1; i <= n; i++) {
41         if (fa[i] == i && cnt[i] > 1) { // 只统计长度大于1的环
42             ret++;
43             maxv = max(maxv, cnt[i]);
44         }
45     }
46     printf("%d %d", ret, ret == 0 ? -1 : maxv);
47     
48     return 0;
49 }
复制代码

 

参考资料

  AcWing 1921. 重新排列奶牛(春季每日一题2022):https://www.acwing.com/video/3835/

posted @   onlyblues  阅读(133)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示