蓝桥杯真题:字串排序

输入输出样例

示例 1

输入

4

输出

bbaa

示例 2

输入

100

输出

jihgfeeddccbbaa

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 256M

思路:把这题拆成两个部分,第一个部分是确定最小长度,第二个部分是确定最小字符序

求最小长度

给出一个定义:交换的次数等于字符串的逆序对个数

例如ba,b比a大,逆序对个数是1,cba,b产生1个,a产生两个,逆序对是3。

于是很自然想到,在长度相同的情况下,逆序数越多,交换次数越多。为了保证逆序对尽可能的多,我们构造的字符串尽量是递减序列。

例如:

长度 len = 1时,我们构造的字符串要形如 a,逆序对个数为 0,交换次数为 0。

当长度 len = 2 时,我们构造的字符串要形如 ba,逆序对个数 1,交换次数为 1。

当长度 len=3 时,我们构造的字符串要形如 cba,逆序对个数为 3,交换次数为 3。

当长度 len=26时,我们构造的字符串得为 zyxwvutsrqponmlkjihgfedcba,逆序对个数为 325,交换次数为 325。

题目中的要求是V最大是10000,所以这个325是远远达不到要求的,所以势必有字符是要重复的。

那接下来我们考虑一个新的问题,在只有a,b,c的情况下,构造出一个长度为7,交换次数最多的字符串,怎么想呢?给定一个字符串,插入一个字符,逆序对增加的个数=前面比该字符小的字符数量 + 后面比该字符大的字符数量。所以呢,每次我们只要插入当前字符串中字符个数最少的字符就好了~所以我们有三种方案cccbbaa,ccbbbaa,ccbbaaa,字符序最小的话,当然是ccbbaaa了。

不过我们现在的问题是:在给定的长度为len的字符串,其最大逆序数个数是多少?为什么要求这个呢,因为当我们找到第一个长度len,其最大逆序数>=V时,我们就可以从这个长度产生字符序最小的字符串,而这个len也就是最小的长度。

那回到正题,有了上边的例子,我们明白了,一个长度为len的字符串里边,要达到最大逆序数,每个字符个数最好是平均分配的,又要字符序最小,个数多的应该是字符顺序小的。字符数量为len/26+1(向下取整)的字符有len%26​个,字符数量为len/26的字符有26-len%26个。那给定len,最大的交换次数的公式如下所示:

解释一下每一项吧,拿前半部分来说,含义是抽取每一个长度为len/26+1的字母,用这个长度乘剩下的长度也就是len-(len/26+1),这样就是该字母产生的逆序对,由于每个

代码如下所示:

int get_max(int len){
    return ((len - (len / 26 + 1)) * (len / 26 + 1) * (len % 26) + (26 - len % 26) * (len / 26) * (len - len / 26)) / 2;
}

确定最小字符序

在上面我们已经求出了最小的长度len,也就是第一个len使得max(len)=X>=V,那么我们肯定也可以改变字符顺序,构造出逆序对个数为X,X-1......0的字符串。

于是我们可以从字符串的第一个位置往后构造,具体为:

从小到大枚举字符ch(ch∈[a~z]),判断当前位置的字符为ch是否可以构造出长度为len,且逆序对大于等于V的字符串。

  • 我们设当前位置为pos,那么1~pos-1的位置已经构造好了,那么当前选择字符ch可以新增的逆序对个数就是1~pos-1中比ch大的字符个数
  • 对于pos+1~len的位置,我们总共要添加len-pos个字符,且要保证这些字符能够产生最多的逆序对。我们可以暴力点一个一个插入。若插入字符x,则产生的逆序对个数(最多)=(1~pos中大于x的字符数量)+(pos后添加的总字符数量-字符x的数量)

最后若第pos个位置放置ch可以构造出长度为len,逆序对个数大于等于V的字符串,我们就令第pos个位置的字符串为ch,继续构造,否则在pos放下一个字符ch+1,继续构造。

具体代码如下所示:

#include<bits/stdc++.h>
using namespace std;
int V , len , now , cnt[27] , sum[27];
int get_max(int len){
    return ((len - (len / 26 + 1)) * (len / 26 + 1) * (len % 26) + (26 - len % 26) * (len / 26) * (len - len / 26)) / 2;
}
bool check(int x , int n){
    memset(cnt , 0 , sizeof(cnt));//cnt记录后面n个位置放置的对应字符数量
    int add1 = 0 , add2 = 0;
    for(int j = 26 ; j >= x + 1 ; j --) add1 += sum[j];//1~pos-1里边比当前插入字符大的字符数量
    sum[x] ++ ;//当前字符数量增加1
    for(int L = 1 ; L <= n ; L ++){
        //ma保存最大逆序对个数 L-1-cnt[j]+num
        //L-1-cnt[j]是当前字符之后的比当前字符小的字符数量的最大值
        //num是1~pos+L-1前的比当前放置字符字典序大的字符数量
        int ma = -1 , pos = 0 , num = 0;
        for(int j = 26 ; j >= 1 ; j --){
            if(L - 1 - cnt[j] + num > ma){
                ma = L - 1 - cnt[j] + num;
                pos = j;
            }
            num += sum[j];
        }
        add2 += ma , cnt[pos] ++;
    }
    if(now + add1 + add2 >= V) {
        now += add1;
        return true;
    }
    else {
        sum[x] -- ;
        return false;
    }
}
signed main()
{
    string ans = "";
    cin >> V;
    for(int i = 1 ; ; i ++) {
        if(get_max(i) >= V){
            len = i;
            break ;
        }
    }
    for(int i = 1 ; i <= len ; i ++){
        for(int j = 1 ; j <= 26 ; j ++){
            if(check(j , len - i)){
                ans += char(j + 'a' - 1);
                break ;
            }
        }
    }
    cout << ans << '\n';
    return 0;
}

转载自蓝桥云课中的思路~

其实其中第pos之后的每个位置选择一个字符颇有点动态规划的味道,很nb的思路很好的一题~

  • 44
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值