acwing 整理书籍和交换座位(环图+贪心)

秋季每日一题 2023

5198. 整理书籍

书架上有若干本书排成一排。每本书要么是大型书(用 L 表示),要么是中型书(用 M 表示),要么是小型书(用 S 表示)。
我们希望所有书能够从大到小有序排列,也就是说,所有大型书都在左侧,所有中型书都在中间,所有小型书都在右侧。为此,你可以进行任意次交换操作,每次可以任选两本书并交换它们的位置。请你计算,为了让所有书按要求有序排列,至少需要进行多少次交换操作。
输入格式
共一行,包含一个由 L、M、S 构成的字符串,表示初始时每个位置上的书的类型。
输出格式
一个整数,表示所需要的最少交换操作次数。
数据范围
输入字符串的长度范围 [1,5×105]。
输入样例1:
LMMMS
输出样例1:
0
样例1解释
无需任何操作,初始排列已经符合要求。
输入样例2:
LLSLM
输出样例2:
2
样例2解释
一种最佳方案如下:

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

考察算法:环图,贪心

/*本题采用环图的概念来做出理论解释;
  根据输入可以得出序列的初始排列,同时根据题意可以得出序列的目标排列
  由某位置i的 初始点指向目标序列点 可构成一个环图; 为了达到目标序列,也即该图需要改造成这N个点的自环图
*/
#include<bits/stdc++.h>
using namespace std;
const int N = 5*100000 + 5;
int main()
{
    string str;
    cin>>str;

    int n = str.length();    //n可以理解为环图中顶点的个数
    int a[N] = {0};
    int s[3] = {0};   //用来记录三个数字各有多少个,降低时间复杂度

    for(int i = 0;i < n;i ++) {        //将初始的字母序列转化为数字序列a[]
        char ch = str[i];
        if(ch == 'L') a[i] = 0;
        else if(ch == 'M') a[i] = 1;
        else a[i] = 2;
        s[a[i]] ++;
    }

    int b[N] = {0};   //用b[] 来记录目标序列

    for(int i = 0,k = 0;i < 3;i ++){
        for(int j = 0;j < s[i];j ++,k ++){
            b[k] = i;
        }
    }

    int e[3][3] = {0};
    for(int i = 0;i < n;i ++){
        e[a[i]][b[i]] ++;    //用矩阵记录下环图,e[i][j] 中存储的是从i节点到j节点的边数
    }

    //接下来计算当前环图中最多可以输出多少环(由于每进行一次交换操作,最多可以增加一个环,所以交换次数 >= n - m)
    //若要交换次数最少,则需要数出来的环数是当前图的最大值(先数所有小环再数大环)==>复习图论知识
    int m = 0;   //用来记录当前环图输出的环数

    for(int i = 0;i < 3;i ++){
        for(int j = i;j < 3;j ++){
            int cnt = min(e[i][j],e[j][i]);  //两点之间的最大环数取决于两点间的最小边数
            m += cnt;
            e[i][j] -= cnt;
            e[j][i] -= cnt;
        }
    }

    //小环计算完毕,现在计算大环(连接所有点的大环)
    m += e[0][1] + e[1][0];  //正向大环和逆向大环不可能同时存在(但不确定保留的是正向环还是逆向环),此时必定可以拆解为N个节点两两构成的N个小环

    cout<<n - m;
    return 0;
}

5221. 交换座位 原题链接

圆桌上有 N个座位,顺时针依次编号为 1∼N。每个座位上都坐着一个人,每个人都属于以下三个组之一:A 组、B 组、C组。如果一个组内的所有成员都连续相邻的坐在一起,该组就会感到满意。我们希望通过交换操作使得所有组都感到满意。每次交换操作可以任选两人交换座位。请你计算,至少需要进行多少次交换操作才能使得所有组都感到满意。
注意:由于是圆桌,所以座位 1和座位 N是相邻的。
输入格式:
共一行,包含一个长度为 N的由 A,B,C组成的字符串,其中的第 i个字符表示初始时第 i个座位上的人的组别。
输出格式:
一个整数,表示所需进行的最少交换操作次数。
数据范围:
1≤N≤106
输入样例:
BABCBCACCA
输出样例:
2
样例解释
一种最佳方案为:

  • 首先,令座位 1和座位 7上的人交换座位,所有人座次变为 AABCBCBCCA。
  • 然后,令座位 4和座位 7上的人交换座位,所有人座次变为 AABBBCCCCA。

考察算法:环图,贪心,破环成链
在上题的基础上,本题的核心考察和整理书籍一致,将相同的编号移到相邻位置。从题设条件来说,本题从上题的链式变为了环状,需要破环成链
A,B,C 相邻在链式上做全排,有6种不同的情况:ABC, ACB, BAC, BCA ,CAB, CBA
在环上而言只分为两类:ABC(BCA,CAB)和 ACB(CBA,BAC)两种

//与整理书籍不同的是,对于接收到的初始序列,并不能构成环图,需要先确定在环图中的切点,破环成链,再用
//整理书籍中的方法
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 2000010;

int n;
char str[N];
int s[3][N];   //一个前缀和数组,用来记录某个字母在区间[0,N]上出现的次数
int cnt[3];    //记录每一个字母出现的次数

//用来计算在区间U上,出现了多少个数字x
int get(PII u, int x)
{
    return s[x][u.second - 1] - s[x][u.first - 1];
}

 //统计在以下标u作为首发位置 长度为N的序列,最少需要替换多少次可以符合题意
int work(int u,int a,int b,int c)
{
   int x = cnt[a], y = cnt[b], z = cnt[c];  //计算三个区间长度
   PII A(u, u + x), B(u + x,u + x + y), C(u + x + y, u + n);   //结合首发位和区间长度划分好三个区间,区间A(B/C)应该全为a(b/c)
   //注意这三个区间都是左开右闭的区间

   int ans = get(A,a) + get(B,b) + get(C,c);   //先计算自环

   //相当于计算两两点形成的小环(一共三个点)
   ans += min(get(A,b), get(B,a));
   ans += min(get(A,c), get(C,a));
   ans += min(get(B,c), get(C,b));

   ans += get(A, b) + get(B, a) - 2 * min(get(A, b), get(B, a));
   //相当于执行整理书籍的e[i][j] -= cnt; e[j][i] -= cnt; 步骤

   return n - ans;

}

int main()
{
    scanf("%s", str + 1);
    n = strlen(str + 1);   //为了前缀和统计方便,从下标1开始读入数据
    memcpy(str + 1 + n, str + 1, n);  //是个环形,为了判断更加便捷,直接copy一份放在末尾

    //进行初始化
    for(int i = 1;i <= 2 * n;i ++){
        //将字母转化为数字
        int t = 0;
        if(str[i] == 'B') t = 1;
        else if(str[i] == 'C') t  = 2;

        //注意这里前缀和的使用
        for(int j = 0;j < 3;j ++){
            s[j][i] = s[j][i - 1];
        }

        if(i <= n) cnt[t] ++;   //统计各个字母出现的次数
        s[t][i] ++;
    }

    int ans = n;
    for (int i = 1; i <= n; i ++ ){
        ans = min(ans,work(i, 0,1, 2));
        ans = min(ans,work(i, 0,2, 1));
    }

    cout<<ans;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值