整理书籍

整理书籍

书架上有若干本书排成一排。

每本书要么是大型书(用 L 表示),要么是中型书(用 M 表示),要么是小型书(用 S 表示)。

我们希望所有书能够从大到小有序排列,也就是说,所有大型书都在左侧,所有中型书都在中间,所有小型书都在右侧。

为此,你可以进行任意次交换操作,每次可以任选两本书并交换它们的位置。

请你计算,为了让所有书按要求有序排列,至少需要进行多少次交换操作。

输入格式

共一行,包含一个由 LMS 构成的字符串,表示初始时每个位置上的书的类型。

输出格式

一个整数,表示所需要的最少交换操作次数。

数据范围

输入字符串的长度范围 [1,5×105]

输入样例1:

LMMMS

输出样例1:

0

样例1解释

无需任何操作,初始排列已经符合要求。

输入样例2:

LLSLM

输出样例2:

2

样例2解释

一种最佳方案如下:

  • 第一步,交换 S 和 M,序列变为 LLMLS
  • 第二步,交换 M 和它右边的 L,序列变为 LLLMS 。

 

解题思路

  看到选择任意两个位置交换来使得序列有序,很容易想到全排列的置换群。不过要用全排列的置换群的做法的话要保证没用重复元素出现,很明显这题不适用。不过实际上还是会用到环图,做法也是很类似的。

  令LMS分别对应012。假设原始序列是a,排序后的序列是b,依次遍历每一个元素,让aibi连一条边,就会得到一个环图。例如有a=[0,0,2,0,1],对应的就有b=[0,0,0,1,2],得到的图如下:

  可以发现构造图的方式与构造全排列置换群是一样的,建边方式都是pii。不过这个问题中不同种类的元素最多只有3种,因此图中最多有3个节点(可以类比,当不同种类的元素最多有n种,那么图中最多有n个节点)。同时每个节点的入度与出度都是一样的,因为同一个值在ab中出现的次数都一样,因此这个图也是一个欧拉图,而欧拉图也是由一堆环构成的。

  我们最终要将序列a变成b,意味着要将图变成n个自环,与全排列的置换群一样,每交换两个元素最多让一个环裂变成两个环。假设当前有m个环,由于最终要变成n个自环,因此至少要交换nm次。可以发现要使得nm尽可能小,就要让m尽可能的大,即对于构造出的原图中要找到尽可能多的环。

  可以发现在这个问题中环的长度只有三种,即长度为1的自环,长度为2的环,以及长度为3的大环。为此我们应该从长度最小的环开始找起,因为环的长度越小则用到的边也越少,剩的边越多得到的环可能会更多。

  定义3×3的矩阵e,其中ei,j表示从节点i指向j的边的数量(即图的邻接矩阵表示)。那么长度为1的环的数量就是e0,0+e1,1+e2,2,能得到长度为2的环的最大数量是min{e0,1,e1,0}+min{e1,2,e2,1}+min{e0,2,e2,0}。然后在图中删除已选出来的边,由于删边后还是保证图中每个节点的入度和出度相同,因此图中就只剩下长度为3的大环,且任意两点间边的数量都相同,所以长度为3的环的数量就是max{e0,1,e1,0}(可能是逆时针的大环,或是顺时针的大环,不可能同时出现否则会存在长度为2的环)。

  AC代码如下,时间复杂度为O(nlogn)

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 5e5 + 10;
 5 
 6 char s[N];
 7 int a[N], b[N];
 8 int e[3][3];
 9 
10 int main() {
11     scanf("%s", s);
12     int n = strlen(s);
13     for (int i = 0; i < n; i++) {
14         int t = 0;
15         if (s[i] == 'M') t = 1;
16         else if (s[i] == 'S') t = 2;
17         a[i] = b[i] = t;
18     }
19     sort(b, b + n);
20     for (int i = 0; i < n; i++) {
21         e[a[i]][b[i]]++;
22     }
23     int ret = 0;
24     for (int i = 0; i < 3; i++) {
25         ret += e[i][i];
26         for (int j = i + 1; j < 3; j++) {
27             int t = min(e[i][j], e[j][i]);
28             ret += t;
29             e[i][j] -= t, e[j][i] -= t;
30         }
31     }
32     ret += max(e[0][1], e[1][0]);
33     printf("%d", n - ret);
34     
35     return 0;
36 }
复制代码

  可以开个哈希表用计数排序优化,时间复杂度就降到了O(n)。AC代码如下:

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 5e5 + 10;
 5 
 6 char s[N];
 7 int a[N], b[N];
 8 int e[3][3], cnt[3];
 9 
10 int main() {
11     scanf("%s", s);
12     int n = strlen(s);
13     for (int i = 0; i < n; i++) {
14         int t = 0;
15         if (s[i] == 'M') t = 1;
16         else if (s[i] == 'S') t = 2;
17         a[i] = t;
18         cnt[t]++;
19     }
20     for (int i = 0, k = 0; i < 3; i++) {
21         for (int j = 0; j < cnt[i]; j++){
22             b[k++] = i;
23         }
24     }
25     for (int i = 0; i < n; i++) {
26         e[a[i]][b[i]]++;
27     }
28     int ret = 0;
29     for (int i = 0; i < 3; i++) {
30         ret += e[i][i];
31         for (int j = i + 1; j < 3; j++) {
32             int t = min(e[i][j], e[j][i]);
33             ret += t;
34             e[i][j] -= t, e[j][i] -= t;
35         }
36     }
37     ret += max(e[0][1], e[1][0]);
38     printf("%d", n - ret);
39     
40     return 0;
41 }
复制代码

 

参考资料

  AcWing 5198. 整理书籍(秋季每日一题2023):https://www.acwing.com/video/4931/

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