【每日一题】补档 CF1799C. Double Lexicographically Minimum | 构造 | 简单

题目内容

原题链接

给定一个长度为 n n n 的只有小写字母的字符串 s s s ,随意排列这个字符串,使得字符串正着和反着的最大字典序最小。

数据范围

  • 1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1n105
  • s s s 中都是小写字母

题解

我们保证首尾字符尽可能相等,且字符尽可能小。

统计每个字符的出现次数。

记录最小字符 a a a 出现了 c a ca ca 次,大于最小字符 a a a 的字符 b b b 出现了 c b cb cb 次,大于 b b b 的字符 c c c 出现了 c c cc cc 次。

  • c a = 1 , c b = 0 ca=1,cb=0 ca=1,cb=0 ,那么直接放
  • c a ≥ 2 ca\geq 2 ca2 ,那么首尾放 a a a 即可
  • c a = 1 , c b = 1 ca=1,cb=1 ca=1,cb=1 ,那么一边放 a a a 一边放 c c c 即可,剩余部分从 c c c 一边开始从小到大放
  • c a = 1 , c b > 1 ca=1,cb>1 ca=1,cb>1
    • c c = 0 cc=0 cc=0 ,那么两边放 b b b,把 a a a 留在最中间
    • c c > 0 cc>0 cc>0 ,那么一边放 a a a 一边放 b b b,剩余部分从 b b b 一边开始从小到大放。
      c c > 0 cc>0 cc>0 如果不放 a a a ,那就是放两个 b b b
      考虑放 a a a 与不放 a a a ,对于 a a a 这边来说不是决定最大字典序的模块,放了 a a a ,我就可以将剩余字符都按从小到大的顺序给 b b b 一边。但是如果不放 a a a ,我就要拿两个 b b b 放到两边,本来我的最小的是 b b . . . c c . . . d d . . . bb...cc...dd... bb...cc...dd... 这种形式,现在少了一个就成了 b c c . . . d d . . . bcc...dd... bcc...dd... 。那么这个 a a a 放到哪,后面一定是接一个从最大字符到最小字符的。但是我本来是 b b c c . . . d d . . . bbcc...dd... bbcc...dd... 的形式,变成了 b c c . . . d d . . . bcc...dd... bcc...dd... 形式,字典序就变大了,所以此时是一边放 a a a 一边放 b b b 最优解。

代码,

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
char s[N];
char ans[N];
int cnt[26];
int n;

void op(int& x, int& y, int i, int j) {
    ans[x] = i + 'a';
    ans[y] = j + 'a';
    cnt[i] -= 1;
    cnt[j] -= 1;
    x += 1;
    y -= 1;
}

void solve() {
    memset(cnt, 0, sizeof cnt);
    scanf("%s", s + 1);
    n = strlen(s + 1);

    if (n == 1) {
        puts(s + 1);
        return ;
    }

    for (int i = 1; i <= n; ++i) {
        cnt[s[i] - 'a'] += 1;
    }

    ans[n + 1] = 0;
    int i = 1, j = n;
    while (i < j) {
        int id[3] = {-1, -1, -1};
        for (int c = 0; c < 26; ++c) {
            if (cnt[c] > 0) {
                if (id[0] == -1) id[0] = c;
                else if (id[1] == -1) id[1] = c;
                else if (id[2] == -1) id[2] = c;
                else break;
            }
        }

        if (id[0] != -1 && cnt[id[0]] >= 2) {
            op(i, j, id[0], id[0]);
            continue ;
        }

        if (id[0] != -1 && id[1] != -1 && cnt[id[0]] == 1 && cnt[id[1]] == 1) {
            op(i, j, id[0], id[1]);
            break;
        }

        if (id[2] == -1 && cnt[id[1]] >= 2) {
            op(i, j, id[1], id[1]);
            continue ;
        } else {
            op(i, j, id[0], id[1]);
            break;
        }
    }

    for (int k = i, c = 25; k <= j; ++k) {
        while (c > 0 && cnt[c] == 0) c -= 1;
        ans[k] = c + 'a';
        cnt[c] -= 1;
    }

    reverse(ans + 1, ans + n + 1);

    printf("%s\n", ans + 1);
}

int main()
{
    int T = 1;
    scanf("%d", &T);
    while (T--) {
        solve();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值